home *** CD-ROM | disk | FTP | other *** search
/ Gamers Delight 2 / Gamers Delight 2.iso / Aminet / game / role / AMDoc_0_9.lha / AMDoc / ProgConcepts.txt < prev    next >
Text File  |  1995-01-21  |  107KB  |  2,376 lines

  1. AmigaMUD, Copyright 1995 by Chris Gray
  2.  
  3.  
  4.     Some of the Concepts and Techniques in AmigaMUD
  5.  
  6. This file will attempt to be a bit more of a tutorial than the
  7. "Progamming.txt" and "Builtins.txt" files, which are reference
  8. material. Here, I will cover some of the areas of special interest
  9. when progamming in AmigaMUD, including:
  10.  
  11.     utility routines
  12.     parsing
  13.     generating good English output
  14.     how effects work
  15.     security issues
  16.     how to code efficiently in AmigaMUD
  17.     limitations of the system
  18.  
  19. I will list the names of builtin functions that are relevant to the
  20. topic. See file "Builtins.txt" for individual descriptions of the
  21. functions. See file "Scenario.txt" for more details on how the
  22. standard scenario works, and how to program within its framework.
  23.  
  24.  
  25. Utility Functions
  26.  
  27.  
  28. Recall from "Programming.txt" that AmigaMUD includes types "string",
  29. and  "int". Several utility functions are generally useful in dealing
  30. with those types:
  31.  
  32.     Capitalize - capitalize the first letter of a string
  33.     IntToString/StringToInt/StringToPosInt - conversion
  34.     Index - search for one string in another
  35.     Length - length of a string
  36.     StringReplace - replace a substring of a string with another
  37.     Strip - strip quotation marks from a string
  38.     SubString - take a substring
  39.     Trim - trim leading and trailing spaces from a string
  40.  
  41. A few others are just generally useful:
  42.  
  43.     Count - return the number of elements in a list
  44.     Date/DateShort - return the current time and date
  45.     Execute - execute a string as an AmigaDOS command
  46.     FileXXX routines - provide access to AmigaDOS files on the host
  47.     SetSeed/GetSeed/Random - deal with pseudo-random numbers
  48.     StringToAction - compile a string into a function
  49.     Time - return the current time as a count of seconds
  50.  
  51. There are also a number of builtins to query or change the status of
  52. the server:
  53.  
  54.     ClientsActive - return 'true' if any player clients are active
  55.     Log - add a message to the "MUD.log" file
  56.     NewCreationPassword - allow SysAdmin to change that password
  57.     RunLimit - set/return the execution time run limit
  58.     ServerVersion - return the version of the server
  59.     SetContinue - control definition of erroneous functions
  60.     SetMachinesActive - control activity of machines
  61.     SetRemoteSysAdminOK - enable/disable remote SysAdmin logins
  62.     SetSingleUser - enable/disable single-user (adventure) mode
  63.     ShowCharacter - show one character's status
  64.     ShowCharacters - show the status of existing characters
  65.     ShowClients - show the status of the active clients
  66.     ShutDown - request shutdown of server
  67.     Trace - add an entry to the debugging trace buffer
  68.     Note - a copyright notice
  69.  
  70. Here is a group of builtins that deal with characters. More are
  71. described in the sections dealing with player characters, machines and
  72. agents:
  73.  
  74.     Editing - tell if the active character is currently editing
  75.     EditProc - start editing a proc (action)
  76.     EditString - start editing a string
  77.     GetString - get a string from the user with a requester
  78.     It - return a handy global variable
  79.     Me - return the active agent (machine or player)
  80.     MeCharacter - return the active character
  81.     NewCharacterPassword - change the player's password
  82.     Normal - switch out of wizard mode
  83.     PrivateTable - return the character's private table
  84.     Quit - request that the client terminate
  85.     SetIt - set the handy global variable
  86.     SetMeString - set the string naming the active client
  87.     SetPrompt - set the prompt for the active client
  88.     TrueMe - return the active agent, even if ForceAction is used
  89.     WizardMode - switch into wizard mode
  90.  
  91. A further set of functions are classed as utility functions since they
  92. don't properly fit into any other categories, but they are often
  93. related to other categories:
  94.  
  95.     DumpThing - dump a thing in all its gory detail
  96.     FindKey - trust SysAdmin and show what a code might be
  97.     PublicTable - return the system-wide public table
  98.     SetEffectiveTo - change the "effective character"
  99.     SetEffectiveToNone - remove the "effective character"
  100.     SetEffectiveToReal - reset the "effective character"
  101.  
  102.  
  103. Output Functions
  104.  
  105.  
  106. The following builtins can be considered as utility routines, but are
  107. classed separately to make them easier to find. They relate to doing
  108. text output from AmigaMUD. The first set are routines which can be
  109. used to produce nice looking English language output. These are the
  110. main ones that would need to be replaced to produce a non-English
  111. version of AmigaMUD:
  112.  
  113.     AAn - insert "a" or "an" in front of a word, as appropriate
  114.     FormatName - convert from "internal form" to English form
  115.     GetIndent - return the current indentation setting
  116.     IsAre - insert "is" or "are" into a string, as appropriate
  117.     Pluralize - simple attempt to pluralize a word
  118.     PrintAction - pretty-print a function
  119.     PrintNoAts - control an anti-spoofing feature
  120.     SetIndent - set indent amount for pretty output
  121.     SetPrefix - set a prefix for output lines
  122.     TextHeight - set/query text output height
  123.     TextWidth - set/query text output width
  124.  
  125. These routines actually do output in various ways:
  126.  
  127.     ABPrint - print to "all but" two agents in a given location
  128.     APrint - print to all active agents
  129.     IPrint - print an int to the active agent
  130.     NPrint - an anti-spoofing print to the active agent
  131.     OPrint - print to others in the same room
  132.     Print - standard print to the active agent
  133.     SPrint - print to a specific agent
  134.  
  135.  
  136. Parsing Functions
  137.  
  138.  
  139. In AmigaMUD, there is a function associated with each character which
  140. is passed each line of input typed by the player. This function is
  141. free to handle the input lines as it sees fit. Thus, it is possible to
  142. write whatever kind of parser is desired. However, it is a lot easier,
  143. and usually sufficient, to use the facilities provided in the AmigaMUD
  144. system to make parsing easier. Note that, unfortunately, these
  145. facilities are currently keyed to the English language and it style. I
  146. welcome detailed specifications or example code of how to do similar
  147. handling in other languages.
  148.  
  149. The AmigaMUD server maintains a string variable which is useful during
  150. parsing. This variable cannot be relied upon outside of the handling
  151. of a single server event, e.g. the parsing of an input line, an action
  152. called for a machine, etc. The variable is referred to as the "tail
  153. buffer", since it contains the tail of the input line after it has
  154. been handled for a "VerbTail" verb (see later). The following
  155. functions deal with the tail buffer:
  156.  
  157.     GetTail - return the current contents of the tail buffer
  158.     GetWord - strip off and return the next "word" in the tail buffer
  159.     SetSay - check for a "says" form, putting text into tail buffer
  160.     SetTail - set the tail buffer to the passed string
  161.     SetWhisperMe - check for "whispers", and put rest in tail buffer
  162.     SetWhisperOther - check for "whispers", put rest in tail buffer
  163.  
  164. There are a few concepts that must be understood in order to make good
  165. use of the AmigaMUD parsing facilities. One of those, which is also
  166. discussed in the "Building.txt" document, is the internal form used
  167. for object names. This form is simple, but fairly general. The basic
  168. idea is to take the English form of a noun-phrase, which is a series
  169. of adjectives followed by a noun, and store it in a form which does
  170. not contain any spaces, and in which the noun is first. The simplest
  171. example is then just a noun all by itself. Adjectives can be added
  172. after a semicolon, and separated by commas. E.g.
  173.  
  174.     noun
  175.     noun;
  176.     noun;adjective
  177.     noun;adjective,adjective
  178.     noun;adjective,adjective,adjective
  179.  
  180. If there is more than one form for the noun, then the other forms can
  181. be given after the first, separated by commas. E.g.
  182.  
  183.     noun1,noun2
  184.     noun1,noun2,noun3;adjective,adjective
  185.  
  186. When handling these forms, the AmigaMUD parsing tools are able to
  187. automatically handle simple plural forms. When the plural forms are
  188. not simple enough, they can be given explicitly, by including other
  189. forms of the noun phrase, separated from previous forms by a period.
  190. E.g.
  191.  
  192.     noun;adjective.noun;adjective,adjective
  193.     noun1,noun2;adjective.noun3;adjective,adjective.noun4;adjective
  194.  
  195. Only the first form ("noun1,noun2;adjective" in the last example) is
  196. ever printed out, so it is possible to include nonsensical or improper
  197. forms in other alternatives, in order to make parsing of irregular
  198. English forms work. The builtin "FormatName" is used to convert from
  199. one of these internal forms into an English form extern form. E.g. if
  200. passed the last example, "FormatName" would return "adjective noun1".
  201.  
  202. These internal forms are the forms that are stored on AmigaMUD
  203. "things" representing objects, as the names of those objects. Then,
  204. "FormatName" is used to print out the name of the object, "MatchName"
  205. is used to match a single internal form against an internal form with
  206. several alternatives, and "FindName" is used to find a "thing" on a
  207. list of things, which has an internal form name which matches the
  208. internal form given. This latter use is the key link between the
  209. parsing of input commands and the AmigaMUD database. There are a few
  210. builtin functions for dealing with internal name forms:
  211.  
  212.     HasAdjective - determine if a given adjective is present
  213.     MatchName - match a simple name form against a complex one
  214.     SelectName - select one simple name form from a complex one
  215.     SelectWord - select one word from an internal form
  216.  
  217. Another important concept in AmigaMUD parsing is that of the
  218. "grammar". A grammar is a lot like a table, in that it is indexed by
  219. strings and contains a set of values associated with those strings.
  220. The values in a grammar, however, are not of any of the standard types
  221. in AmigaMUD. Instead, they are completely internal forms, which are
  222. known only to the AmigaMUD parsing and grammar handling code. Grammars
  223. contain verbs and their definitions, as supplied by the scenario. All
  224. of the main parsing and grammar related functions in AmigaMUD take a
  225. grammar as their first argument, identifying which set of verbs they
  226. are to work with. Wizards can create and manipulate new grammars, and
  227. can thus set up entire schemes for parsing. For example, the standard
  228. scenario creates and uses grammars for the build commands in general,
  229. and for "build room" and "build object" commands in particular. There
  230. are a number of utility builtins for dealing with grammars:
  231.  
  232.     CreateGrammar - create a new grammar
  233.     FindAnyWord - find a word in a grammar
  234.     FindWord - find a non-synonym word in a grammar
  235.     ShowWord - show the definition of a word in a grammar
  236.     ShowWords - show all of the words in a grammar
  237.     Synonym - enter a synonym into a grammar
  238.     Word - enter a non-verb word into a grammar
  239.  
  240. Another aspect of AmigaMUD parsing that it is important to understand
  241. is the flow of control that happens during parsing. There can be
  242. variations on this scheme, but the scheme used in the standard
  243. scenario is shown here. Note that, under normal circumstances, the
  244. function t_util/parseInput is set as the input-line handler on all
  245. active characters, and is thus called by the system whenever an input
  246. command line arrives for that character.
  247.  
  248.     - parseInput checks for some special cases, such as aliases setup
  249.     by the player, commands starting with a quotation mark (") or
  250.     a colon (:), commands special to the current location, etc.
  251.  
  252.     - parseInput passes the input line to the builtin function
  253.     "Parse", passing the main grammar maintained by the scenario
  254.     as the first parameter to "Parse".
  255.  
  256.     - Parse handles strings containing multiple input commands,
  257.     separated by periods or semicolons. It calls a lower level
  258.     routine for each one. If that lower level routine returns
  259.     'false', then Parse stops processing the commands. Parse
  260.     returns the number of commands successfully handled.
  261.  
  262.     - the internal processing routine strips off the first word of
  263.     each command. It looks that word up in the grammar. If the
  264.     word is found, it does any handling required by the type of
  265.     the verb found (none for a "VerbTail" verb, getting a pair of
  266.     noun phrases and a separator word for a "Verb2" verb, etc.)
  267.  
  268.     - if all is well, the internal processing routine calls the
  269.     (interpreted) scenario routine associated with the matched
  270.     verb form. It will pass zero, one or two internal name form
  271.     strings to that routine, which it has parsed from the input
  272.     command.
  273.  
  274.     - the scenario verb routine attempts to find the objects in the
  275.     database that the internal name forms are referring to. This
  276.     usually involves using "FindName" with those forms on the list
  277.     of things at the current location, and the list of things that
  278.     the user is carrying. If appropriate objects are found, the
  279.     verb routine will do the semantic action associated with the
  280.     verb, such as picking the object up. The verb routine returns
  281.     'true' to signal that all is well, else 'false' if there was
  282.     some kind of problem. Most verb routines will also look for
  283.     and call any relevant functions attached to the found objects,
  284.     the character, or the current room. These routines can perform
  285.     additional actions, or can prevent the main action from
  286.     happening.
  287.  
  288.     - the internal processing routine may call the scenario verb
  289.     routine multiple times if more than one object noun-phrase is
  290.     detected in the input command.
  291.  
  292. Thus, the call sequence can look like this:
  293.  
  294.     - system automatically calls scenario input handler routine
  295.     - scenario routine calls server internal Parse builtin
  296.     - Parse calls scenario verb routine
  297.     - verb routine calls several server internal builtins
  298.  
  299. There are four forms of verb supported by the AmigaMUD parsing code.
  300.  
  301. The simplest form to understand is the "VerbTail" form. In this verb
  302. form, Parse will simply remove the verb itself from the command, and
  303. will put the rest of the command into the tail buffer, where it can be
  304. manipulated with "GetTail" and "GetWord". For example, commands like
  305. "alias" and "say", which do not interpret the entire command, are
  306. usually done as VerbTail forms. This form can also be used when the
  307. parsing facilities in AmigaMUD are not adequate to handle the verb
  308. directly. For example:
  309.  
  310.     private proc v_tail()bool:
  311.     Print("The tail of the command is '" + GetTail() + "'.\n");
  312.     true
  313.     corp;
  314.     VerbTail(G, "tail", v_tail).
  315.     Parse(G, "tail this. tail all the other stuff. tail of dragon").
  316.  
  317. would produce output:
  318.  
  319.     The tail of the command is 'this'.
  320.     The tail of the command is 'all the other stuff'.
  321.     The tail of the command is 'of dragon'.
  322.  
  323. As with other verb routines, the routine used for a VerbTail verb
  324. returns 'true' to indicate that all is well, and 'false' to indicate
  325. that something is wrong and that parsing of the rest of the commands
  326. in the input line should be abandoned.
  327.  
  328. The next verb form is the "Verb0" form. This form accepts no (zero)
  329. noun phrases on the command, but accepts an optional "separator word"
  330. as part of the command. The optional word is so named because of its
  331. function in the "Verb2" form (see later). The Verb0 verb must be given
  332. by itself as a command, perhaps with its separator if one is given.
  333. The prototype of Verb0:
  334.  
  335. proc utility Verb0(grammar theGrammar; string theVerb; int separatorCode;
  336.     action theAction)void
  337.  
  338. indicates that the separatorCode is an int. This is the code for that
  339. word in the grammar. Each word in a grammar is assigned a unique code,
  340. which can be found by looking the word up in the grammar with
  341. "FindWord" or "FindAnyWord". If the word is used in other
  342. circumstances as a verb (like "up" in "stand up"), then it will be
  343. entered into the grammar as a verb. If the word is not so used,
  344. however, then it can be entered into the grammar using "Word", which
  345. adds the word to the grammar, but not as a verb. No word in a grammar
  346. has code 0, so that value is used to indicate that no separator word
  347. is required or allowed. Examples:
  348.  
  349.     private proc v_dream()bool:
  350.     if Me()@p_pAsleep then
  351.         Print("You dream pleasant dreams.\n");
  352.         true
  353.     else
  354.         Print("You are not asleep.\n");
  355.         false
  356.     fi
  357.     corp;
  358.     Verb0(G, "dream", 0).
  359.  
  360.     private proc v_sitUp()bool:
  361.     if Me()@p_pLyingDown then
  362.         Print("You sit up.\n");
  363.         Me() -- p_pLyingDown;
  364.         true
  365.     else
  366.         Print("You are not lying down.\n");
  367.         false
  368.     fi
  369.     corp;
  370.     Verb0(G, "sit", FindWord(G, "up")).
  371.  
  372.     private proc v_sitDown()bool:
  373.     if Me()@p_pLyingDown then
  374.         Print("You are already lying down.\n");
  375.         false
  376.     elif Me()@p_pSittingDown then
  377.         Print("You are already sitting down.\n");
  378.         false
  379.     else
  380.         Print("You sit down.\n");
  381.         Me()@p_pSittingDown := true;
  382.         true
  383.     fi
  384.     corp;
  385.     Verb0(G, "sit", FindWord(G, "down")).
  386.  
  387. Here, verb "dream" has no separator word. Verbs "sit up" and "sit
  388. down" do, and those words are used to decide which form of the verb
  389. "sit" is being used. If "sit" is used without a separator word, then
  390. a verb form without a separator word will be used, but if there isn't
  391. one, then one of "sit up" or "sit down" will be picked, in no defined
  392. way. The programmer should define a "sit" without a separator word,
  393. to pick which of the two should be used, or to print an error message.
  394.  
  395. A "Verb1" verb is one which accepts a direct object, to which the verb
  396. should be applied. E.g. "take the red ball", "launch rocket", etc.
  397. Such verbs can also have a separator word, and it is accepted either
  398. before the object or after it. The object is any sequence of words -
  399. the system does not try to interpret them in any way. An occurrence of
  400. any of "a", "an" or "the" at the start of the noun phrase is stripped
  401. off by the parser. The sequence of words for the direct object is
  402. terminated by the separator word, the end of the command or by a comma
  403. or the word "and". The sequence of words is taken to be the English
  404. form of a noun phrase, i.e. a sequence of adjectives followed by a
  405. noun. It is converted to the internal form, consisting of the noun, a
  406. semicolon, and the adjectives, separated by commas.
  407.  
  408. When a Verb1 verb routine is called by the parser, it is passed the
  409. internal form of the noun phrase as its single string parameter. If
  410. more than one noun phrase is given, separated by commas or the word
  411. "and", then the verb routine will be called once for each noun phrase
  412. in sequence, or until it returns 'false', indicating that the rest of
  413. the noun phrases in the command, and the rest of the commands in the
  414. input string, should be abandoned. Note that if a Verb1 verb is
  415. matched by the parser, but no noun phrase was given in the command,
  416. then the parser will call the verb routine once with an empty string
  417. as parameter. The verb routine should check for and handle this case.
  418.  
  419. It is up to the verb routine to decide if the noun phrase is a valid
  420. one for the circumstances, and to find an object in the database that
  421. can be referred to by the noun phrase. Typically, this will be a
  422. matter of looking for a matching internal form object name in the
  423. objects in the player's inventory, and in the objects in the room.
  424. This can be done using 'FindName', and perhaps some of the other
  425. 'Find' builtins. Many verb routines will look for and handle a
  426. routine attached to the object, the player, or the room, in order to
  427. allow special-case processing. Here is a fairly complete example:
  428.  
  429.     private G CreateGrammar().
  430.     private p_pCarrying CreateThingListProp().
  431.     private p_oName CreateStringProp().
  432.     private pEatCheck CreateActionProp().
  433.     private p_pFoodCount CreateIntProp().
  434.     private p_oFoodValue CreateIntProp().
  435.     private p_pStomachAche CreateBoolProp().
  436.  
  437.     private apple1 CreateThing(nil).
  438.     apple1@p_oName := "apple;juicy,red".
  439.     apple1@p_oFoodValue := 5.
  440.  
  441.     private apple2 CreateThing(nil).
  442.     apple2@p_oName := "apple;sour,green".
  443.     apple2@p_oFoodValue := 2.
  444.     private proc apple2Eat(thing theApple)status:
  445.     Me()@p_pStomachAche := true;
  446.     continue
  447.     corp;
  448.     apple2@pEatCheck := apple2Eat.
  449.  
  450.     private apple3 CreateThing(nil).
  451.     apple3@p_oName := "apple;rosy,red".
  452.     private TheWickedWitch CreateThing(nil).
  453.     private proc apple3Eat(thing theApple)status:
  454.     if Me() = TheWickedWitch then
  455.         Print("Stupid - you just wasted a poison apple!\n");
  456.     else
  457.         Print("Ack! The rosy red apple was poison!\n");
  458.         Me()@p_pFoodCount := 0;
  459.     fi;
  460.     succeed
  461.     corp;
  462.     apple3@pEatCheck := apple3Eat.
  463.  
  464.     private rock1 CreateThing(nil).
  465.     rock1@p_oName := "rock;small,round".
  466.  
  467.     Me()@p_pCarrying := CreateThingList().
  468.     AddTail(Me()@p_pCarrying, apple1).
  469.     AddTail(Me()@p_pCarrying, apple2).
  470.     AddTail(Me()@p_pCarrying, apple3).
  471.     AddTail(Me()@p_pCarrying, rock1).
  472.  
  473.     private room CreateThing(nil).
  474.     SetLocation(room).
  475.  
  476.     private proc v_eat(string what)bool:
  477.     thing me, theFood;
  478.     string foodName;
  479.     status st;
  480.     action specialAction;
  481.  
  482.     if what = "" then
  483.         Print("You must say what you want to eat.\n");
  484.         false
  485.     else
  486.         me := Me();
  487.         foodName := FormatName(what);
  488.         st := FindName(me@p_pCarrying, p_oName, what);
  489.         if st = fail then
  490.         Print(AAn("You aren't carrying", foodName) + ".\n");
  491.         false
  492.         elif st = continue then
  493.         Print(Capitalize(foodName) + " is ambiguous.\n");
  494.         false
  495.         else
  496.         theFood := FindResult();
  497.         st := continue;
  498.         specialAction := me@pEatCheck;
  499.         if specialAction ~= nil then
  500.             st := call(specialAction, status)(theFood);
  501.         fi;
  502.         if st = continue then
  503.             specialAction := Here()@pEatCheck;
  504.             if specialAction ~= nil then
  505.             st := call(specialAction, status)(theFood);
  506.             fi;
  507.         fi;
  508.         if st = continue then
  509.             specialAction := theFood@pEatCheck;
  510.             if specialAction ~= nil then
  511.             st := call(specialAction, status)(theFood);
  512.             fi;
  513.             if st = continue then
  514.             if theFood@p_oFoodValue ~= 0 then
  515.                 Print("You eat the " + foodName + ".\n");
  516.                 me@p_pFoodCount := me@p_pFoodCount +
  517.                 theFood@p_oFoodValue;
  518.                 DelElement(me@p_pCarrying, theFood);
  519.                 true
  520.             else
  521.                 Print("You can't eat the " + foodName +
  522.                 ".\n");
  523.                 false
  524.             fi
  525.             else
  526.             st = succeed
  527.             fi
  528.         else
  529.             false
  530.         fi
  531.         fi
  532.     fi
  533.     corp;
  534.     Verb1(G, "eat", 0, v_eat).
  535.  
  536.     Parse(G, "eat").
  537.     Parse(G, "eat apple. eat rock").
  538.     Parse(G, "eat carrot").
  539.     Parse(G, "eat orange").
  540.     Parse(G, "eat rock").
  541.     Parse(G, "Eat the small round rock.").
  542.     Parse(G, "eat juicy red apple, green apple and rosy red apple").
  543.  
  544. The (numbered) output from sourcing this example is:
  545.  
  546.  1 > source test.m
  547.  2 Sourcing file "test.m".
  548.  3 You must say what you want to eat.
  549.  4 ==> 0
  550.  5 Apple is ambiguous.
  551.  6 ==> 0
  552.  7 You aren't carrying a carrot.
  553.  8 ==> 0
  554.  9 You aren't carrying an orange.
  555. 10 ==> 0
  556. 11 You can't eat the rock.
  557. 12 ==> 0
  558. 13 You can't eat the small round rock.
  559. 14 ==> 0
  560. 15 You eat the juicy red apple.
  561. 16 You eat the green apple.
  562. 17 Ack! The rosy red apple was poison!
  563. 18 ==> 1
  564. 19 > d Me().
  565. 20 thing, parent <NIL-THING>, owner SysAdmin, useCount 1, propCount 5,
  566. 21     ts_public:
  567. 22   p_pName: "SysAdmin"
  568. 23   p_pIcon: {16380, 1073890242, 1206011394, 600055908, 669258696,
  569. 24 268961808, 69206592, 25165824}
  570. 25   p_pCarrying: {apple3, rock1}
  571. 26   p_pFoodCount: 0
  572. 27   p_pStomachAche: true
  573.  
  574. This sample is a complete example, which can be run on an empty
  575. database as created by MUDCre. It starts out by creating a grammar,
  576. and giving it symbol "G" in the user's (most likely SysAdmin) private
  577. symbol table. Next, a number of properties are defined. These are
  578. attributes which can be attached to things. All of the property
  579. symbols start with the letter "p" as a memory aid. Those which are to
  580. be attached only to players start with "p_p", and those which are to
  581. be attached only to objects start with "p_o". Since this is only a
  582. small sample, and not a complete scenario, the properties are used for
  583. illustration, and are not fully implemented.
  584.  
  585. Following the properties, the example code creates four objects, and
  586. gives them to the active character, by adding them to an inventory
  587. list (property p_pCarrying) attached to the character. The first item,
  588. "apple1", is nothing special, and has a food value of 5. The second is
  589. a sour apple, and has an attached "pEatCheck" action, "apple2Eat".
  590. This routine will be called dynamically by the "eat" verb. The third
  591. object, "apple3", has a slightly more drastic "pEatCheck" routine. The
  592. thing "TheWickedWitch" is an example only - it has no properties and
  593. exists only to be tested against in "apple3Eat". The fourth and last
  594. object, "rock1", is again very simple, and it doesn't even have a food
  595. value, and hence is not considered to be "edible" by this example. The
  596. five lines starting with "Me()@p_pCarrying := ..." create a new list
  597. of things, attach it to the active character (the one sourcing the
  598. file), and append the four newly created objects to that list. The
  599. final lines before "v_eat" create a dummy location and move the active
  600. character to that location. This is needed since "v_eat" uses the
  601. value returned by the "Here" builtin.
  602.  
  603. "v_eat" is the central portion of this example. It is a fairly
  604. complete Verb1 routine to handle attempts by the player to eat things.
  605. It starts out by declaring a bunch of local variables. Local variables
  606. are used here for two reasons. One is the convenience of typing just a
  607. variable name instead of a longer expression. The other reason is that
  608. of efficiency - it is quicker in AmigaMUD to reference a local
  609. variable than it is to call some function, even a builtin function.
  610.  
  611. v_eat first tests to see if the string it was passed is the empty
  612. string. Recall from the previous discussion of Verb1 verbs, that if
  613. the verb is used without an accompanying noun phrase, the verb handler
  614. routine (here v_eat) is called with an empty string as argument. v_eat
  615. simply prints a helpful message, and returns 'false', indicating that
  616. some kind of error has occurred. Note that it is a good idea to phrase
  617. such error comments as suggestions, rather than as questions like
  618. "What do you want to eat?", since then there is no confusion on the
  619. part of the player over what is acceptable input.
  620.  
  621. v_eat then gets a pointer to the active player, and saves it in a
  622. quicker-to-access local variable. It then gets a printable version of
  623. the object name string that the player entered. Recall that the
  624. internal form passed to verb handler routines can be turned into a
  625. normal English form using the "FormatName" builtin.
  626.  
  627. The next line, containing the call to "FindName" is the crucial link
  628. between input commands and the database representing the "world". It
  629. looks for a thing with property "p_oName" whose value matches the
  630. string it is passed, which here is the internal form name passed to
  631. this verb handler by the AmigaMUD parsing code. FindName returns a
  632. value of type status, with values indicating:
  633.  
  634.     fail - no thing with a matching name was found in the list
  635.     succeed - one thing with a matching name was found
  636.     continue - more than one thing with a matching name was found
  637.  
  638. So, if the result of FindName, stored in local variable "st", is
  639. 'fail', then no matching name was found, i.e. the player is not
  640. carrying anything that matches the noun-phrase given in the command.
  641. This example (and the standard scenario) does not attempt to pick an
  642. alternative in the case of multiple matches, so it prints an error
  643. message if FindName returned 'continue'. Note the use of "AAn" and
  644. "Capitalize" in these messages in order to produce neat English
  645. output. If FindName returned 'succeed', then it saves a pointer to the
  646. found thing, and that pointer is retrieved by calling "FindResult".
  647.  
  648. After retrieving the found thing, i.e. identifying the object in the
  649. world that the command is referring to, all that remains is to perform
  650. the "eat" operation on that object. Many scenarios will have a number
  651. of special cases for such an operation. Rather than coding them
  652. explicitly in v_eat, it is easier, cleaner and less error prone to use
  653. an "object-oriented" approach and attach those special actions
  654. directly to the object. Such special actions can also be attached to
  655. the character, and to rooms. So, the code in v_eat checks for special
  656. actions first on the character, then in the room the player is in, and
  657. finally on the object being eaten. These special actions are here
  658. assumed to return a value of type status, indicating:
  659.  
  660.     succeed - the object is eaten - no further checks should take
  661.     place, but the eating action is successful
  662.     fail - the object cannot be eaten for some reason
  663.     continue - nothing has happened which affects the later processing
  664.     of this action - continue on
  665.  
  666. Note the uses of the "call" construct. This is how to call a function
  667. obtained dynamically in AmigaMUD. The "call" specifies the function to
  668. be called and the type it is supposed to return, and is followed by a
  669. parenthesized list of any parameters to the called function. The
  670. function's return value and parameter count and types will be checked
  671. at runtime, and execution will be aborted if any do not match.
  672.  
  673. If none of the special actions (pEatCheck's) stop the eating
  674. operation, then v_eat looks for a "p_oFoodValue" property on the
  675. object. If the object does not have one, it is considered to be not
  676. eatable and v_eat complains. Otherwise, the food value is added to
  677. the character's food count, which is the normal action here of eating
  678. something. After an item has been eaten, it is deleted from the list
  679. of things being carried by the character.
  680.  
  681. The odd-looking line reading "st = succeed" is the return value of the
  682. v_eat function (which returns a bool) if one of the special actions
  683. does not return 'continue'. The result of the last special action
  684. executed is stored in local variable "st". If that value is 'fail',
  685. then the eating failed, else it succeeded, so the expression tests for
  686. "st = succeed" to generate the appropriate bool value.
  687.  
  688. Following the definition of v_eat is the following line:
  689.  
  690.     Verb1(G, "eat", 0, v_eat).
  691.  
  692. This adds verb "eat" to grammar "G", specifying v_eat as the function
  693. to be called to execute the verb. The value 0 for the "separator word"
  694. indicates that no special extra word is needed or allowed with "eat".
  695.  
  696. The next seven lines pass some test strings to "Parse" to try out the
  697. "eat" verb. Normally, such input lines come from players, via their
  698. "input action". The first test checks out our handling of a missing
  699. noun phrase, and produces output lines 3 and 4 (recall that "Parse"
  700. returns the number of successfully handled commands, and that the
  701. AmigaMUD client programs print any result of an interactively entered
  702. expression.
  703.  
  704. Next, we try to eat any apple and then the rock. v_eat finds that
  705. "apple" is ambiguous, says so, and returns 'false'. This tells Parse
  706. to not execute any more commands in its parameters, thus it does not
  707. execute the "eat rock" command, and there is no complaint here about
  708. trying to eat the rock.
  709.  
  710. Output lines 7 and 8 come from trying to eat a "carrot" - the
  711. character is not carrying anything with a name which matches that.
  712. Similarly for "orange". Note that the use of "AAn" in the complaint
  713. message results in proper use of "a" versus "an". Eating a rock
  714. doesn't work because it has no p_oFoodValue property. This is an
  715. example of how attempting to retrieve a non-existant property from a
  716. thing is not an error, but produces a default value, here 0. Output
  717. lines 13 and 14 come from the second attempt to eat the rock. Note
  718. that Parse has automatically handled the capitalized first letter of
  719. "Eat", the period at the end of the command, and the use of the
  720. article "the".
  721.  
  722. Output lines 15 - 18 are the most interesting. Here we are actually
  723. eating something. Again, Parse automatically handles the list of noun
  724. phrases, calling v_eat sequentially with each one. Eating the "juicy
  725. red apple", which matches only "apple1", works without incident and
  726. adds that object's food value to the character's food count. Eating
  727. the "green apple" (matches apple2) appears to work just as well, but
  728. actually will give the character a stomach ache. This is because the
  729. special action routine "apple2Eat" returns 'continue', indicating
  730. that processing should continue as normal.
  731.  
  732. The special action for apple3, matched by "rosy red apple", returns
  733. 'succeed', indicating that the apple has been eaten, and no further
  734. processing of eating this object should happen. Since all three of the
  735. calls to v_eat returned 'true', Parse considers the entire command to
  736. have been executed successfully, and returns a count of 1.
  737.  
  738. Output lines 20 - 27 show the state of the character after executing
  739. the various "eat" commands. The character is still carrying the poison
  740. apple and the rock. The special action "apple3Eat", since it returns
  741. 'succeed', should have deleted itself from the character's list of
  742. things being carried. The character has a food count of 0, set that
  743. way by apple3Eat, and has flag p_pStomachAche, set by apple2Eat.
  744.  
  745. The third kind of verb supported by the AmigaMUD parsing routines is
  746. the "Verb2" verb. The general form of the input accepted for this verb
  747. form is:
  748.  
  749.     verb {noun-phrase}* separator noun-phrase
  750.  
  751. E.g.
  752.  
  753.     Put the sword, the brass lamp and the book into the trophy case.
  754.  
  755. Here, the verb is "put", and the separator word is "into". The direct
  756. objects are "the sword", "the brass lamp", and "the book", and the
  757. single indirect object is "the trophy case". A verb action for a
  758. Verb2 verb has two parameters. The first is the direct object, and the
  759. second is the indirect object (both in internal form as usual). When
  760. more than one direct object is given, as in the example shown, the
  761. verb action is called repeatedly, with the various direct objects, and
  762. the single fixed indirect object. A Verb2 handler will never be called
  763. with either parameter being an empty string, since Parse checks for
  764. that case and handles it directly.
  765.  
  766. I do not give an example of a Verb2 handler here. Refer to file
  767. "verbs.m" in the standard scenario sources for examples.
  768.  
  769. Note that the separator word and the presence of objects can be used
  770. to decide which form of a verb to use. Thus, a scenario could define
  771. all of:
  772.  
  773.     Verb0(G, "stand", FindWord("up"), v_standUp).
  774.     Verb0(G, "stand", FindWord("down"), v_standDown).
  775.     Verb1(G, "stand", FindWord("on"), v_standOn1).
  776.     Verb2(G, "stand", FindWord("on"), v_standOn2).
  777.  
  778. and thus handle input commands like:
  779.  
  780.     stand up
  781.     stand down
  782.     stand on the bench
  783.     stand the statue on the pedestal
  784.  
  785. The builtins useful for setting up verbs and for parsing are:
  786.  
  787.     FindName - key input/database link - find a named item
  788.     FindResult - return a found thing
  789.     GetNounPhrase - get a noun phrase from a string
  790.     ItName - return the original direct object internal form
  791.     Parse - parse a string using a grammar
  792.     Verb - return the original form of the verb used
  793.     Verb0 - add a Verb0 verb to a grammar
  794.     Verb1 - add a Verb1 verb to a grammar
  795.     Verb2 - add a Verb2 verb to a grammar
  796.     VerbTail - a tail verb to a grammar
  797.     WhoName - return the original indirect object internal form
  798.  
  799.  
  800. Generating Good English Output
  801.  
  802.  
  803. Many MUD players are quite picky about the text they read. They are
  804. disdainful of graphics output, and desire all MUDs to read like well-
  805. written novels. Few, if any, MUDs meet the desires of such players.
  806. However, it is a good idea to generate good output text from a MUD, so
  807. that a few vocal players can't make it sound like a terrible MUD. This
  808. includes such simple things as spelling words correctly, getting the
  809. grammar correct, proper capitalization, etc. The standard AmigaMUD
  810. scenario is by no means perfect in this respect, but I have tried to
  811. handle most cases. There are some builtin functions in AmigaMUD that
  812. can help:
  813.  
  814.     Capitalize - capitalize the first letter in a string
  815.     AAn - insert either "a" or "an", and spaces as appropriate,
  816.     between two strings, based on whether or not the first letter
  817.     in the second string is a vowel or a consonant
  818.     IsAre - this routine takes four string parameters. The second
  819.     string can be empty, or can be a word like "no". If the second
  820.     string is empty and the third string ends in "s", then IsAre
  821.     inserts "are some" between the first and third strings. If the
  822.     second string is empty and the third string does not end in
  823.     "s", then IsAre inserts either "is a" or "is an" between the
  824.     second and third strings, depending on whether the third
  825.     string begins with a vowel or not. If the second string is not
  826.     empty, then IsAre inserts either "is" or "are" between the
  827.     first and second strings. IsAre also inserts all needed
  828.     spaces between strings.
  829.     Pluralize - attempts to pluralize the string passed
  830.  
  831. Even with these functions, it is often not possible to generate proper
  832. output in all cases. Sometimes the scenario programmer will put up
  833. with bad output, and sometimes he will re-arrange things so that
  834. different phrasing, with correct grammar, can be used. The important
  835. thing is to avoid obvious errors, and to show that some care has been
  836. taken in producing output.
  837.  
  838.  
  839. Effects
  840.  
  841.  
  842. The term "effects" in AmigaMUD refers to a variety of output events
  843. that occur in the MUD client program, but which are governed by
  844. scenario code running in the MUDServ server program. Simple text
  845. output is not considered to be an effect. Effects include graphics
  846. output, sound output, voice output, music output (not yet supported),
  847. icon displaying and mouse-button displaying and definition.
  848.  
  849. These various effects are implemented via a simple interpreter built
  850. in to the MUD program. The interpreter has an 'if' construct (although
  851. there is only one thing that can be tested), and can do subroutine
  852. calls (calls to other effect routines from a main effect routine). The
  853. effect routines are sent from the server to the client as a stream of
  854. bytes which are the "machine code" for the effects "machine", and are
  855. executed entirely in the MUD client, as directed by scenario code
  856. running in the server.
  857.  
  858. The client keeps effects routines that it has been sent, unless it
  859. runs out of memory or the user explicitly flushes something out of the
  860. "effects cache" maintained by the client. The server keeps track of
  861. which effects routines are known by which active client program, so
  862. that they do not have to be transmitted unneccessarily. In addition,
  863. the client will not discard an effect that is called by another effect
  864. that it has in its cache - the upper level one must be freed first.
  865. This is so that once the execution of an effect routine starts in the
  866. client program, it cannot fail because of a missing effect routine.
  867.  
  868. If the client program ever does find itself trying to execute an
  869. effect routine it does not know, it will simply do nothing. This can
  870. happen because the scenario code must explicitly define the contents
  871. of an effect, thus sending the definition to the client, when it sees
  872. that the client does not know the needed effect. In other words, it
  873. isn't foolproof - a buggy scenario can mess up effects so that they
  874. don't appear when desired.
  875.  
  876. The execution of an effect routine in the client is triggered when
  877. scenario code in the server uses the "CallEffect" builtin when it is
  878. not in the middle of defining an effect routine. In the latter case,
  879. the "CallEffect" is an effects subroutine call to the called effect.
  880. The definition of an effects subroutine is started when the scenario
  881. code executes the "DefineEffect" builtin, and ends when a matching
  882. call to "EndEffect" occurs. The DefineEffect call is given an
  883. identifier by which the effect is known. These identifiers should all
  884. be unique, else effects will be messed up. The easiest way to do this
  885. is to use a unique-id generator routine in the scenario. The standard
  886. scenario has routine "NextEffectId" for this purpose.
  887.  
  888. As an example, here is the definition of a simple effects routine
  889. which will clear the graphics screen and draw a blue box on it:
  890.  
  891. private EXAMPLE_EFFECT_ID 1.
  892.  
  893. private proc setupExampleEffect()void:
  894.  
  895.     DefineEffect(nil, EXAMPLE_EFFECT_ID);
  896.     GClear(nil);
  897.     GSetAPen(nil, C_BLUE);
  898.     GAMove(nil, 100, 20);
  899.     GRectangle(nil, 200, 50, true);
  900.     EndEffect();
  901. corp;
  902.  
  903. We can now trigger the effect using:
  904.  
  905. CallEffect(nil, EXAMPLE_EFFECT_ID).
  906.  
  907. The builtin routine "KnowsEffect" returns 'true' if the given active
  908. client knows the specified effect, thus allowing the scenario code to
  909. test whether or not it has to define the effect for that client. Thus,
  910. the sequence for defining and using an effect is usually like:
  911.  
  912. private EXAMPLE_EFFECT_ID 1.
  913.  
  914. private proc doExampleEffect()void:
  915.  
  916.     if not KnowsEffect(nil, EXAMPLE_EFFECT_ID) then
  917.     DefineEffect(nil, EXAMPLE_EFFECT_ID);
  918.     GClear(nil);
  919.     GSetAPen(nil, C_BLUE);
  920.     GAMove(nil, 100, 20);
  921.     GRectangle(nil, 200, 50, true);
  922.     EndEffect();
  923.     fi;
  924.     CallEffect(nil, EXAMPLE_EFFECT_ID);
  925. corp;
  926.  
  927. This will cause the effect to be executed, with it being defined
  928. before the execution, if needed.
  929.  
  930. Note that in these examples, the 'who' parameter has always been
  931. 'nil'. This directs the effects to the active agent, i.e. the player
  932. on whose behalf the code is being executed. This is the normal
  933. situation for effects which define scenery, etc. for locations in the
  934. scenario. It is possible to use the 'thing' value for some other
  935. active agent, and the effect will be defined and executed for that
  936. agent instead of for the active one. Do not try to mix agents inside
  937. an effect definition, however, as this can result in havoc! Also, it
  938. is a good idea to do all effects for a given client before moving on
  939. to another client, to minimize the number of separate messages that
  940. must be sent to the various clients. The server buffers up effects
  941. requests and sends as much as possible in large batches. These batches
  942. are flushed to the clients when the client for effects changes, or the
  943. processing of the original event (input line, mouse click, timer
  944. driven action, etc.) completes.
  945.  
  946. Most effects can be considered to have taken place as soon as the
  947. effects routine is called. Some, however, take time to execute. This
  948. is the case for voice output, sound output and music output. Thus, for
  949. these effects, the main builtin which triggers them is given another
  950. identifying integer for that effect. When the effect is done (e.g. the
  951. speech completes, or the sound sample ends), the client will send a
  952. message to the server indicating that, and the server can then call an
  953. "effect complete" action on the character, thus notifying the scenario
  954. code of the completion. The scenario code can then start another such
  955. effect, thus having things going on continuously. Such ongoing effects
  956. can also be aborted by a call to "AbortEffect" in the server, which
  957. specifies the identifier of the ongoing affect to abort.
  958.  
  959. The following kinds of effects are possible:
  960.  
  961.     general graphics:
  962.     - simple drawing primitives
  963.     - loading of IFF ILBM backgrounds
  964.     - insertion of smaller IFF ILBM images
  965.     - overlaying of IFF ILBM brushes
  966.  
  967.     sound:
  968.     - speech using the Amiga's "narrator.device"
  969.     - playing IFF 8SVX sound samples
  970.  
  971.     mouse input control:
  972.     - control of visible "mouse buttons"
  973.     - control of invisible "mouse regions"
  974.  
  975.     special purpose graphics:
  976.     - icon control
  977.     - cursor control
  978.     - colour palette control
  979.     - text in graphics window
  980.     - control and use of rectangular "tiles"
  981.  
  982. The user of the MUD client program can control, via menus or function
  983. keys, whether or not certain types of effects are active. This
  984. information is available to scenario code in the server, so that
  985. messages for disabled effects are not sent to the client. Also, the
  986. scenario code can use alternative methods (such as simple text output)
  987. to show the user what is happening. To allow scenario code to properly
  988. customize effects sent to a client, a number of builtin routines are
  989. available to return information about the effects capabilities of the
  990. client (note that the standard scenario does not make use of some of
  991. this information):
  992.  
  993.     GColours - return the number of colours the client can display
  994.     GCols - return the horizontal pixel width of the output area
  995.     GOn - query if the client is currently handling graphics
  996.     GPalette - query if the client has a changeable colour palette
  997.     GRows - return the vertical pixel height of the output area
  998.     GType - return the type of the client (e.g. "Amiga")
  999.     MOn - query if the client is currently handling music
  1000.     QueryFile - check for a file under AmigaMUD: on the client
  1001.     SOn - query if the client is currently handling sound
  1002.     VOn - query if the client is currently handling voice
  1003.  
  1004. The builtin functions for adding primitive graphics operations to an
  1005. effect routine (or doing them right away) are:
  1006.  
  1007.     GAMove - move drawing point to a given absolute position
  1008.     GCircle - draw an outline or filled circle
  1009.     GClear - clear the graphics area to pen 0
  1010.     GEllipse - draw an outline or filled ellipse
  1011.     GPixel - set a single pixel
  1012.     GPolygonEnd - end the drawing of a polygon
  1013.     GPolygonStart - start the drawing of a polygon
  1014.     GRDraw - draw a line in a relative direction
  1015.     GRectangle - draw an outline or filled rectangle
  1016.     GRMove - move drawing point in a relative direction
  1017.  
  1018. Note that the clipping of circles and ellipses is not very good in the
  1019. MUD client, so make sure all of them are within the graphics window.
  1020. The capabilities of the polygon drawing are limited to those of the
  1021. Amiga's AreaXXX calls. On most display devices used with Amiga's, the
  1022. aspect ratio of the image is not square, so that a circle appears as
  1023. an ellipse.
  1024.  
  1025. The following miscellaneous effects routines are often used with
  1026. simple graphics effects, but can be used in other circumstances as
  1027. well:
  1028.  
  1029.     GResetColours - reset graphics palette to the default
  1030.     GScrollRectangle - scroll a rectangle of the graphics area
  1031.     GSetColour - define one colour of the graphics palette
  1032.     GSetPen - select the active graphics pen
  1033.  
  1034. Builtins for dealing with IFF ILBM files (which must exist on the
  1035. client machine, not on the server) are:
  1036.  
  1037.     GLoadBackGround - load and display a background image
  1038.     GSetImage - set a default image
  1039.     GShowBrush - overlay a brush onto the current graphics
  1040.     GShowImage - display a rectangular piece of an image
  1041.  
  1042. Loading a background replaces the graphics area with the background
  1043. image loaded from a file. The image is clipped to fit within the
  1044. display area, and any display area not covered by the image is left
  1045. unchanged. If the IFF file contains a colour palette, then that
  1046. palette will replace the active pallete used for the graphics screen.
  1047. Note that the this can make the pointer and the mouse buttons look
  1048. awful, so care should be used in choosing palletes for backgrounds.
  1049. The name of a background is just a file name, which will be evaluated
  1050. relative to "AmigaMUD:BackGrounds/" on the client machine.
  1051.  
  1052. GSetImage is used to set a default image. If GShowImage is given an
  1053. empty string as the name of the image file to use, then it will use
  1054. the default image instead. This is useful when a single IFF ILBM file
  1055. contains several smaller images which are to be pieced together to
  1056. create an entire picture. Images are clipped against both the display
  1057. area and the image in the file. Any palette stored with an image file
  1058. is ignored. Image names are relative to "AmigaMUD:Images/".
  1059.  
  1060. Brushes are clipped against the display area. Any pallete in a brush
  1061. file is ignored. Brushes can have either an explicit stored mask
  1062. plane, or can have a transparent colour indicated. If they have
  1063. neither (i.e. aren't brushes), then they will be blitted rectangularly
  1064. into the window, just like images are. Brush names are relative to
  1065. "AmigaMUD:Brushes/".
  1066.  
  1067. The MUD program caches IFF ILBM files in memory, so that they can be
  1068. referenced repeatedly without disk I/O. This caching takes place in
  1069. the Amiga's "chip" memory, so that the images can be accessed quickly.
  1070. The current set of cached files, of various kinds, can be displayed
  1071. with a menu item in the MUD program.
  1072.  
  1073. Builtins for dealing with sound and voice output are:
  1074.  
  1075.     SPlaySound - start playing an IFF 8SVX sound sample
  1076.     SVolume - set the overall volume for sound playback
  1077.     VNarrate - narrate a set of phonemes
  1078.     VParams - set the overall voice output parameters
  1079.     VReset - reset the voice parameters to default values
  1080.     VSpeak - speak some English text
  1081.     VTranslate - translate English text to phonemes (this is not
  1082.     really an effects routine, since it executes entirely in the
  1083.     AmigaMUD server)
  1084.     VVolume - set the overall volume for speech output
  1085.     AbortEffect - cancel an ongoing effect (sound, speech, music)
  1086.  
  1087. Sound samples names are relative to "AmigaMUD:Sounds/" on the client
  1088. machine. If SMUS music is supported, it will be relative to
  1089. "AmigaMUD:Music/" with instruments from "AmigaMUD:Instruments/".
  1090.  
  1091. Builtins for dealing with "mouse buttons" and "mouse regions" are:
  1092.  
  1093.     AddButton - add a mouse button to the user's graphics window
  1094.     AddRegion - add a mouse region to the user's client
  1095.     ClearButtons - remove all mouse buttons from the client
  1096.     ClearRegions - remove all mouse regions from the client
  1097.     EraseButton - erase a given button from the client
  1098.     EraseRegion - erase a given region from the client
  1099.     SetButtonPen - set a pen to use when drawing mouse buttons
  1100.  
  1101. Note that none of these routines takes an agent as a parameter - they
  1102. all operate only on the active agent. A "mouse button" is a
  1103. rectangular "button" drawn on the graphics screen. It usually contains
  1104. a small amount of text. The user can click on the button with the left
  1105. mouse button, and trigger actions within the MUD. A "mouse region" is
  1106. similar to a button, except that it is an invisible rectangular region
  1107. that the user can click in. The icon editor in the Beauty Shop is a
  1108. mouse region.
  1109.  
  1110. Each button or region has an identifier (an integer), that is supplied
  1111. when it is created. When the user clicks on a mouse button, the
  1112. "button handler" routine associated with the character is called, with
  1113. that identifier as a parameter. If there is no button handler attached
  1114. to the character, then the clicks are ignored. The handler can do what
  1115. it wants - in the standard scenario the standard movement buttons echo
  1116. and execute a movement or "look around" command. When the user clicks
  1117. within a mouse region, the character's "mouse down" handler is called,
  1118. with the identifier of the region, and the relative offset of the
  1119. click within the region. If mouse regions overlap, and the mouse is
  1120. clicked in an overlap area, then the region with the lowest identifier
  1121. is selected and reported. The standard scenario uses a mouse region
  1122. with identifier 1000 over the entire left-half of the graphics window.
  1123. This is used to implement movement by clicking relative to the
  1124. character cursor.
  1125.  
  1126. The following effects functions deal with the character cursor:
  1127.  
  1128.     PlaceCursor - display the cursor at the indicated position
  1129.     RemoveCursor - remove the cursor from the display
  1130.     SetCursorPattern - set the pattern for the cursor
  1131.     SetCursorPen - set which pen to draw the cursor with
  1132.  
  1133. The "character cursor" is a small one-colour bitmap that the MUD
  1134. client program can display to represent the location of the active
  1135. character within an overhead-view map area. Conceptually, this cursor
  1136. is the top-most of the graphics items, so it will appear "over" the
  1137. background image and any icons. The MUD program keeps track of the
  1138. graphics "behind" the cursor, so that the cursor can be moved (taken
  1139. away and put back elsewhere) without having to redraw the entire
  1140. image. The cursor can be up to 16 pixels hight and 16 pixels wide,
  1141. just like icons. The default cursor in MUD is a large cross. The
  1142. standard scenario has been set up assuming a used cursor size of 7
  1143. pixels by seven pixels. A scenario does not have to use a cursor - it
  1144. is only present when requested by the scenario. It would be possible
  1145. to use brushes as a cursor, but the background behind them is not
  1146. automatically saved, so redrawing would be necessary when moving it.
  1147.  
  1148. The following builtins deal with icons:
  1149.  
  1150.     GDeleteIcon - delete an icon from a MUD client
  1151.     GNewIcon - specify a new icon pattern for a character
  1152.     GRedrawIcons - redraw the current set of icons
  1153.     GRemoveIcon - undraw a single icon
  1154.     GResetIcons - clear the set of icons
  1155.     GSetIconPen - set the colour to draw icons with
  1156.     GShowIcon - add an icon to a client
  1157.     GUndrawIcons - undraw all icons from the client
  1158.  
  1159. "Icons" are similar to the cursor in that they are 16 x 16 single-
  1160. colour patterns that are maintained by the MUD program. Conceptually,
  1161. they are behind the cursor, but in front of the main graphics. Like
  1162. the cursor, MUD saves the background imagery behind icons, so they can
  1163. be removed from the display without having to redraw the picture.
  1164. Unlike the cursor, the scenario does not have any control over the
  1165. placement of icons. MUD will place the first one in the top-left
  1166. corner of the graphics screen, the next one to the right of that, etc.
  1167. Since the graphics area is 320 pixels wide and 100 pixels high, there
  1168. is room for 320/16 times 100/16 = 20 times 6 = 120 icons in the
  1169. display. Icons beyond that number are silently ignored. Empty icon
  1170. slots are reused first, so most icons will appear in the top-left
  1171. corner of the display.
  1172.  
  1173. The standard scenario uses icons to represent other characters, both
  1174. player characters and non-player characters, in the same room as the
  1175. player. Players can edit their own icon (which does not show up on
  1176. their display) in the Beauty Shop, just as they can edit their cursor.
  1177. When a character moves from one room to another, that character's icon
  1178. should be removed from the displays of all players in the first room
  1179. and added to the displays of all players in the second room. Thus, the
  1180. various icon calls all take a 'who' parameter to make this easier to
  1181. code in the scenario.
  1182.  
  1183. When a player moves from one room to another, the set of visible icons
  1184. must be replaced. Thus, GResetIcons is available to reset MUD's idea
  1185. of which icons are visible, without having to actually erase them.
  1186. Sometimes the graphics imagery for the room needs to be changed,
  1187. without the player leaving the room. GRedrawIcons can be used to
  1188. redraw the current set of icons over a new background. GUndrawIcons
  1189. can be used to undraw the full set, thus allowing a small change to be
  1190. made in the background image, then followed by GRedrawIcons.
  1191.  
  1192. The pattern for an icon is obtained by MUDServ from the thing for the
  1193. character whose icon is to be displayed. Thus, property "p_pIcon",
  1194. like "p_pName" is predefined in an empty database, and the scenario
  1195. coder should use GNewIcon to change the value of a character's icon,
  1196. rather than assigning directly to that property. Another reason for
  1197. using GNewIcon is that it will immediately send the new definition of
  1198. the icon to any MUDs that have it cached, and those MUDs will
  1199. immediately display the new icon.
  1200.  
  1201. Text can be displayed in the graphics area using:
  1202.  
  1203.     GSetTextColour - set the colour to draw text in
  1204.     GText - draw a text string at the current position
  1205.  
  1206. "Tile" graphics are supported by the AmigaMUD system, even though the
  1207. first release of the standard scenario does not use them. The
  1208. available calls are:
  1209.  
  1210.     GDefineTile - define the appearance and size of a tile
  1211.     GDisplayTile - display the given tile at the current position
  1212.  
  1213. Tile graphics is a way to show a more detailed overhead view image
  1214. without having to create and save a huge image of the entire map area.
  1215. The map is divided into many small rectangles, or tiles, which are
  1216. displayed from a fixed set of such tiles. If the tiles are designed
  1217. carefully, the effect is that of a large hand-drawn image. Usually,
  1218. the map area is much larger than can be displayed in the graphics
  1219. view, so when the player nears the edge of the visible portion, the
  1220. display is scrolled, and a new set of tiles is draw in the exposed
  1221. space. Smooth scrolling does the scrolling one pixel at a time instead
  1222. of one tile at a time. AmigaMUD does not directly support smooth
  1223. scrolling.
  1224.  
  1225. Here is a complete source file which shows a small, non-scrolling
  1226. example of displaying tiles:
  1227.  
  1228.     private t_tiles CreateTable().
  1229.     use t_tiles
  1230.  
  1231.     define t_tiles TILE_WIDTH 32.
  1232.     define t_tiles TILE_HEIGHT 20.
  1233.  
  1234.     define t_tiles TILES_WIDTH 5.
  1235.     define t_tiles TILES_HEIGHT 5.
  1236.  
  1237.     source st:/tiles/town.tile
  1238.     source st:/tiles/trees.tile
  1239.     source st:/tiles/river.tile
  1240.  
  1241.     GDefineTile(nil, 1, TILE_WIDTH, TILE_HEIGHT, makeTownTile()).
  1242.     GDefineTile(nil, 2, TILE_WIDTH, TILE_HEIGHT, makeTreesTile()).
  1243.     GDefineTile(nil, 3, TILE_WIDTH, TILE_HEIGHT, makeRiverTile()).
  1244.  
  1245.     define t_tiles proc makeTerrain()list int:
  1246.     list int terrain;
  1247.     int row, col;
  1248.  
  1249.     terrain := CreateIntArray(TILES_WIDTH * TILES_HEIGHT);
  1250.     for row from 0 upto TILES_HEIGHT - 1 do
  1251.         terrain[row * TILES_WIDTH] := 3;
  1252.         for col from 1 upto TILES_WIDTH - 1 do
  1253.         terrain[row * TILES_WIDTH + col] := 2;
  1254.         od;
  1255.     od;
  1256.     terrain[2] := 1;
  1257.     terrain
  1258.     corp;
  1259.  
  1260.     define t_tiles TileThing CreateThing(nil).
  1261.     define t_tiles TileProp CreateIntListProp().
  1262.     TileThing@TileProp := makeTerrain().
  1263.  
  1264.     define t_tiles proc drawTiles()void:
  1265.     list int terrain;
  1266.     int row, col;
  1267.  
  1268.     terrain := TileThing@TileProp;
  1269.     GAMove(nil, 0, 0);
  1270.     for row from 0 upto TILES_HEIGHT - 1 do
  1271.         for col from 0 upto TILES_WIDTH - 1 do
  1272.         GDisplayTile(nil, terrain[row * TILES_WIDTH + col]);
  1273.         GRMove(nil, TILE_WIDTH, 0);
  1274.         od;
  1275.         GRMove(nil, - TILE_WIDTH * TILES_WIDTH, TILE_HEIGHT);
  1276.     od;
  1277.     corp;
  1278.  
  1279. The '.tile' files simply define a tile as an array of ints:
  1280.  
  1281.     define t_tiles proc makeTownTile()list int:
  1282.     list int tile;
  1283.  
  1284.     tile := CreateIntArray(160);
  1285.     tile[0] := 0x1d1d1d1d;
  1286.     tile[1] := 0x1d1d1d1d;
  1287.     tile[2] := 0x1d1d1d1d;
  1288.     tile[3] := 0x1d1d0301;
  1289.     tile[4] := 0x0101031d;
  1290.     ...
  1291.     tile[155] := 0x1d1d0301;
  1292.     tile[156] := 0x01010303;
  1293.     tile[157] := 0x03030303;
  1294.     tile[158] := 0x03030303;
  1295.     tile[159] := 0x03030303;
  1296.     tile
  1297.     corp;
  1298.  
  1299. In this example, the tile definitions are all created on the server
  1300. and sent to the client via calls to GDefineTile. The MUD client
  1301. programs cache tile definitions just like they do icons and the
  1302. cursor. Thus, the scenario need only send the tile definitions to the
  1303. client once per session. Note, however, that there is no way by which
  1304. the scenario can know if the client has seen a tile yet. Thus, the
  1305. scenario has to keep track of that by itself. Re-sending a tile
  1306. definition does not hurt, but is expensive in terms of communication.
  1307. Builtin "GScrollRectangle" can be used to scroll the tile display.
  1308.  
  1309. Another way to define tiles is to have a file containing them on the
  1310. remote client machine. Then, calls to GSetImage/GShowImage can be used
  1311. to piece the full view together from a single file containing a
  1312. standard set of tiles. GDefineTile/GDisplayTile can then be used for
  1313. special tiles, that are not part of the standard set. This was the
  1314. original plan for the use of tiles in AmigaMUD.
  1315.  
  1316. GDefineTile takes an array of integers as the definition of the tile.
  1317. The array must be of size WIDTH * HEIGHT / 4, where WIDTH and HEIGHT
  1318. are the size of the tile. This gives one byte per pixel in the tile.
  1319. The byte gives the colour of the corresponding pixel of the tile. The
  1320. bytes are supplied by rows, with the colour of the top-left pixel of
  1321. the tile being the high-order byte of the first integer in the array.
  1322. If the tile width is a multiple of 4, then hexadecimal values for the
  1323. integers provide a somewhat readable way of defining the tiles, as in
  1324. the above example.
  1325.  
  1326. Examples of defining effects were given above. The builtin functions
  1327. involved are:
  1328.  
  1329.     CallEffect - call up a previously defined effect
  1330.     DefineEffect - start the definition of an effect
  1331.     EndEffect - end the definition of an effect
  1332.     KnowsEffect - ask if a client knows an effect
  1333.  
  1334. CallEffect is used to call up a previously defined effect. If the
  1335. selected client (the MUD program) does not know an effect with the
  1336. indicated effect-id, then it will simply do nothing. CallEffect is
  1337. like a subroutine call of effects. It can be used from the effects
  1338. "top-level" or from inside some other effects routine. KnowsEffect
  1339. tells the scenario whether or not a client knows an effect. If the
  1340. client does not know the effect, that effect should be defined for the
  1341. client before it is called. EndEffect ends the definition of the
  1342. effect currently being defined. It is like the "corp" to end the body
  1343. of an AmigaMUD function.
  1344.  
  1345. It is possible to nest the definition of effects. This can actually
  1346. happen quite frequently. For example, the effect which draws the
  1347. normal view of the mini-mall in the standard scenario calls on the
  1348. effects routines for vertical, horizontal and diagonal doors. When a
  1349. player first enters the game in the Arrivals Room, the scenario wants
  1350. to run the effect for the mini-mall view. The client does not know
  1351. that effect, which the scenario learns from KnowsEffect, so the
  1352. scenario uses DefineEffect to define the mini-mall view effect. The
  1353. definition of that effect calls scenario routines for the doors, which
  1354. in turn check to see if the client knows the effects for the doors. A
  1355. new client doesn't know those effects either, so the routines all
  1356. define the effects. This happens in the middle of the definition of
  1357. the mini-mall view. Since this is a common occurrence, and is
  1358. difficult for the scenario to work-around, AmigaMUD was made to
  1359. support it, by allowing nested effects definitions.
  1360.  
  1361. The AmigaMUD server is single threaded. That means that it is only
  1362. running one thread of code execution at a time. Thus, it should never
  1363. wait for something to happen in a client, since whatever it is waiting
  1364. for could take a long time, especially if it has to wait for the user.
  1365. Hand-drawn graphics are usually nicer than the kind of graphics that
  1366. can be drawn using effects. So, it is desireable that a scenario call
  1367. up that kind of graphics, from files on the client machine, rather
  1368. than use pictures drawn via effects calls. However, if the client
  1369. machine does not have the needed bitmap image, the drawn effect should
  1370. be displayed. This decision can only be made on the client machine. To
  1371. avoid having the AmigaMUD server wait for the answer to that question
  1372. from a client, the result of that question must also be executed on
  1373. the client. This means that the effects interpreting code in the
  1374. clients needs to be able to support conditional execution of effects.
  1375. Currently this conditional execution is very limited. The builtin
  1376. functions involved are:
  1377.  
  1378.     Else - flip the conditional execution of effects
  1379.     FailText - display text along with the name of the missing file
  1380.     Fi - end a conditional effects section
  1381.     IfFound - start a conditional effects section
  1382.  
  1383. IfFound is the effect that is the condition test. It tests the "found"
  1384. flag, which is set in the client by the GLoadBackGround, GSetImage,
  1385. GShowImage, GShowBrush, SPlaySound, and MPlaySong effects requests.
  1386. These requests all specify the name of a file to be accessed. If the
  1387. file is found on the client, then the "found" flag is set, else that
  1388. flag is cleared. IfFound tells the client to execute the following
  1389. effects requests only if the file was found. The Else effect tells the
  1390. client to reverse the current value of the "found" flag. Thus, if the
  1391. client was currently executing effects, it will stop doing so, and if
  1392. it was not executing effects it will start doing so. The Fi effect
  1393. marks the end of the conditional effect section - effects will always
  1394. be enabled after the Fi effect. Thus, the normal structure of
  1395. conditional effects is like this:
  1396.  
  1397.     GSetImage(client, "file-name");
  1398.     IfFound(client);
  1399.     GShowImage(client, "", iX, iY, iW, iH, dX, dY);
  1400.     Else(client)
  1401.     /* effects code to approximate the image */
  1402.     Fi(client);
  1403.  
  1404. or
  1405.  
  1406.     SPlaySound(client, "file-name", effectId);
  1407.     IfFound(client);
  1408.     Else(client);
  1409.     FailText(client, "text message describing the sound");
  1410.     Fi(client);
  1411.  
  1412. Either the image is shown (the empty string says to use the filename
  1413. set (and loaded) with GSetImage), or the failure string is displayed.
  1414. FailText will include the name of the file that was not found in its
  1415. printout, so that the player can tell what file he/she is missing.
  1416. Note that there is no "not" in the effects condition, so in the second
  1417. example there is nothing between the IfFound and Else calls.
  1418.  
  1419.  
  1420. The Database
  1421.  
  1422.  
  1423. The database in AmigaMUD, stored in files MUD.data and MUD.index,
  1424. contains everything that is permanent about the MUD scenario: rooms,
  1425. objects, characters, players, machines, code, text, etc. The database
  1426. is maintained on disk, and does not have to be all in memory. Thus,
  1427. you can run a very large database without having to have many
  1428. megabytes of memory. The AmigaMUD server program, MUDServ, maintains a
  1429. cache of the most recently used database items. This cache is a
  1430. "write-back" cache. This means that changes to database items are not
  1431. written to the disk immediately. The changes are entered into the
  1432. database cache, and only get written to disk when the database cache
  1433. is flushed. The database cache is flushed to disk when:
  1434.  
  1435.     - the server is shut down
  1436.     - the Flush builtin is called
  1437.     - the MUDFlush program is run
  1438.     - the cache has no room for a needed entry
  1439.  
  1440. In the last case, entries written to disk can then be deleted from the
  1441. cache, to make room for new entries.
  1442.  
  1443. In actual fact, the implementation of MUDServ is quite a bit more
  1444. complicated. There are more levels of caches that are not visible to
  1445. the user or the scenario programmer. One example is this:
  1446.  
  1447.     "things", "properties", etc. have use counts on them, which
  1448.     indicate how many pointers to them exist in the database. This is
  1449.     done so that database entries can be deleted when the last pointer
  1450.     to them is removed. The sequence of actions that happens when a
  1451.     player or machine picks an object up is something like this:
  1452.  
  1453.     - append object to character's inventory
  1454.     - delete object from room's contents
  1455.  
  1456.     For a short period of time the object is on both lists. That means
  1457.     that it's use count is one higher. Almost immediately the use
  1458.     count goes back to what it was before. So, there really is no need
  1459.     to write the object back to disk. MUDServ has a cache of changed
  1460.     thing use counts, and, when flushing the database, will write to
  1461.     the database cache any thing whose use count is different from the
  1462.     one stored on disk. Similar caches exist for other types of
  1463.     entries.
  1464.  
  1465. Something to be very careful of is the fact that a reference to
  1466. something from a local variable or function parameter does not count
  1467. as a reference from the database. Note the order of the operations in
  1468. the "pick up" example just above. The object is added to the inventory
  1469. list before it is removed from the contents list. This is very
  1470. important! If the operations are done in the other order, then if
  1471. there are no other references to the object than the ones involved
  1472. here, it will have no references for a short period of time. This is
  1473. bad, since the system will conclude that the object can be freed, and
  1474. will remove it from the database and reuse the space! If your code
  1475. appears to corrupt the database, check that you have not let go of
  1476. something before you are truly done with it. The reader will certainly
  1477. want to ask why I chose to implement things this way. The answer is
  1478. mostly one of efficiency - changing the use count all the time takes a
  1479. lot of extra instructions in the server. I estimate that typical
  1480. execution would slow down by a factor of two or three, and a lot of
  1481. extra code would have to be added in order to properly make references
  1482. from local variables and parameters count as database references.
  1483.  
  1484. Another fairly important cache is the function cache. When functions
  1485. are stored in the database, it is as a sequence of bytes. This is not
  1486. the form that the interpreter understands. So, when a function needs
  1487. to be interpreted, it must be read from the database and converted
  1488. into the interpretable form. The server maintains a cache of functions
  1489. in this interpretable form. When the server runs low on memory, it
  1490. will delete non-active functions from this cache. Similar caches exist
  1491. for tables and grammars.
  1492.  
  1493. Every entry in the database must be pointed to by some other entry in
  1494. the database, with the sole exceptions being the public table and a
  1495. list of active machines. When a new database is created by the MUDCre
  1496. program, it contains very little. Everything else must be explicitly
  1497. created and pointed to by something in the database. Builtin functions
  1498. exist to create all kinds of database entries:
  1499.  
  1500.     CreateActionList - create a list of actions
  1501.     CreateActionListProp - create a property of that type
  1502.     CreateActionProp - create a property that can reference actions
  1503.     CreateBoolProp - create a boolean property
  1504.     CreateGrammarProp - create a property that references grammars
  1505.     CreateIntArray - create and initialize a list of ints
  1506.     CreateIntList - create an empty list of ints
  1507.     CreateIntListProp - create a property of that type
  1508.     CreateIntProp - create an int property
  1509.     CreateStringProp - create a string property
  1510.     CreateTable - create a new table
  1511.     CreateTableProp - create a property that can reference tables
  1512.     CreateThing - create a new thing
  1513.     CreateThingList - create an empty list of things
  1514.     CreateThingListProp - create a property of that type
  1515.     CreateThingProp - create a property that references other things
  1516.  
  1517. Lists of several types exist in AmigaMUD. In addition to the indexing
  1518. operation that is available in the AmigaMUD programming language,
  1519. several builtin functions deal with lists. They are "generic" in the
  1520. sense that they work with any type of list, and, when appropriate, the
  1521. corresponding type of element:
  1522.  
  1523.     AddHead - insert an element onto the head of a list
  1524.     AddTail - append an element onto the tail of a list
  1525.     DelElement - delete an element from a list
  1526.     FindChildOnList - search for a thing's child in a list
  1527.     FindElement - search for an element in a list
  1528.     FindFlagOnList - search for a flagged thing in a list
  1529.     RemHead - remove the first element from a list
  1530.     RemTail - remove the last element from a list
  1531.  
  1532. There are also a number of builtins that deal with "things" in the
  1533. database:
  1534.  
  1535.     ClearThing - remove all properties from a thing
  1536.     DescribeKey - let SysAdmin find out what a key value is
  1537.     GetThingStatus - return the status of a thing
  1538.     GiveThing - change the owner of a thing
  1539.     IsAncestor - check for an ancestor of a thing
  1540.     Mine - check the ownership of a thing
  1541.     Owner - find the owner of a thing
  1542.     Parent - find the parent of a thing
  1543.  
  1544.  
  1545. Dealing With Player Characters
  1546.  
  1547.  
  1548. Player characters are the entities in AmigaMUD that represent players.
  1549. They are of type 'character', which is one of the basic types in the
  1550. system. Associated with each character are a number of functions,
  1551. which the system will automatically call in appropriate circumstances.
  1552. There is a builtin function to set that function on the active
  1553. character. The builtins all return whatever function was the previous
  1554. value (or 'nil'). Those functions are:
  1555.  
  1556.     SetCharacterActiveAction - set the action which is called when the
  1557.     player re-enters the game. The action is not called when the
  1558.     player first enters the game - see SetNewCharacterAction for
  1559.     that situation. Typically, a scenario will use this action to
  1560.     initialize the graphics for a client, and give the client a
  1561.     description of his/here location, who is nearby, who is in the
  1562.     MUD, etc.
  1563.  
  1564.     SetCharacterButtonAction - set the action which is called when the
  1565.     player clicks on a mouse-click button. The action is passed
  1566.     the code for the button that was clicked. The action will
  1567.     typically echo and execute a standard command like a movement
  1568.     command. The on-line building code in the standard scenario
  1569.     uses many mouse-click buttons for other purposes.
  1570.  
  1571.     SetCharacterEffectDoneAction - set the action which is called when
  1572.     an ongoing effect (sound, voice, music) completes in the
  1573.     client. The action is passed the type and identifier of the
  1574.     effect which has completed.
  1575.  
  1576.     SetCharacterIdleAction - set the action which is called when the
  1577.     player leaves the game. This action can do things like
  1578.     removing light from a room if the leaving player has the only
  1579.     source of light. It will usually tell everyone in the room
  1580.     that the player is leaving.
  1581.  
  1582.     SetCharacterInputAction - set the action which is called when the
  1583.     player enters an input line. Input lines are usually commands
  1584.     which are sent through builtin "Parse", but some special
  1585.     processing is often done. The standard scenario checks for a
  1586.     leading quote (") or colon (:), and handles command aliases.
  1587.  
  1588.     SetCharacterMouseDownAction - set the action which is called when
  1589.     the player clicks within a mouse region. The action is passed
  1590.     the identifier for the region, and the offset of the click
  1591.     within the region. The standard scenario uses this action to
  1592.     handle movement when the player clicks in the left-hand
  1593.     portion of the graphics area. Another such region is used to
  1594.     implement the icon/cursor editor.
  1595.  
  1596.     SetCharacterRawKeyAction - set the action which is called when the
  1597.     player presses a numeric keypad key or the HELP key. The
  1598.     action is passed a keycode for the key pressed. Like mouse
  1599.     buttons, these events are usually made to trigger standard
  1600.     movement or other commands.
  1601.  
  1602.     SetNewCharacterAction - set the action that is executed whenever a
  1603.     newly created character first enters the game. This action is
  1604.     usually used to initialize the character's handlers and any
  1605.     scenario-specific stuff. Note that when this action is called,
  1606.     the character's "active" action will not also be called, so
  1607.     any of the stuff that it does that is also needed here should
  1608.     be done explicitly (or this action can directly call the
  1609.     normal "active" action).
  1610.  
  1611. There are circumstances when "nesting" of some of these handler
  1612. actions is useful. For example, the icon/cursor editor sets up a
  1613. mouse-button handler to handle the three new buttons it displays. It
  1614. is better if it does not assume anything about what the previous
  1615. handler was. So, when it calls SetCharacterButtonAction, it should
  1616. record the returned value, and when it is finished, restore it. Also,
  1617. unless the icon/cursor editor code removes the standard movement
  1618. buttons, the player can still click on them. The code could chose to
  1619. ignore such clicks, but can also simply pass them on to the previous
  1620. routine, which it has saved away. If that previous routine is not the
  1621. standard movement one, it can do the same thing, resulting in a whole
  1622. stack of actions, one of which should understand the mouse click. How
  1623. this sort of thing is handled depends on what the scenario writer
  1624. wants to do.
  1625.  
  1626. Another important use of nesting is that of single-occupancy areas.
  1627. If, for example, a player is in Questor's Office when he/she exits the
  1628. game, it is not good to leave the office locked when there really
  1629. isn't anyone inside it. So, the standard scenario moves the player out
  1630. of the office to the street. This is done by having a nested "idle"
  1631. action. It resets Questor (which is also done when the player leaves
  1632. Questor's Office normally), moves the player outside, resets the idle
  1633. action, and calls the newly reset idle action (if any). The nesting of
  1634. the idle action is done when the player enters the office, by having a
  1635. checker action on the room/direction that leads to the office.
  1636.  
  1637. Such complexities can happen in other cases as well. For example, if
  1638. the player walks out of the Beauty Shop while in the middle of editing
  1639. his/her cursor, what should happen, and how is it achieved? The
  1640. interested player might want to study that code and to experiment to
  1641. see what it does.
  1642.  
  1643. There are number of other builtin functions that deal with player
  1644. characters:
  1645.  
  1646.     BootClient - force the player off, politely
  1647.     CanEdit - can the client do editing?
  1648.     ChangeName - change the player name
  1649.     Character - return the character of the thing
  1650.     CharacterLocation - return the location of the character
  1651.     CharacterTable - return the private table of the character
  1652.     CharacterThing - return the main "thing" of the character
  1653.     ClientVersion - return the version of the client program
  1654.     CreateCharacter - create a new player character
  1655.     DestroyCharacter - destroy a player character
  1656.     IsApprentice - is the player an apprentice?
  1657.     IsNormal - is the player normal (not apprentice or wizard)?
  1658.     IsProgrammer - is the player an apprentice or wizard?
  1659.     IsWizard - is the player a wizard?
  1660.     MakeApprentice - make the player an apprentice
  1661.     MakeNormal - make the player normal
  1662.     MakeWizard - make the player a wizard
  1663.     NukeClient - force the player off, impolitely (and dangerously)
  1664.     SetCharacterLocation - move a character (also SetLocation)
  1665.     ThingCharacter - return the character associated with a thing
  1666.  
  1667.  
  1668. Dealing With Machines
  1669.  
  1670.  
  1671. Machines are the method used in AmigaMUD to cause events to happen
  1672. independent of any player character. Machines can have icons and
  1673. appear in rooms just like player characters can. They can speak,
  1674. whisper, hear, move around, pick things up, etc. Together, players and
  1675. machines are referred to as "agents". The builtin functions discussed
  1676. above under "Dealing With Player Characters" do not apply to machines.
  1677. There are specific functions for dealing with machines, and there are
  1678. a set of functions that work with any agent. The latter ability is
  1679. quite important, as it allows a lot of scenario code to not care
  1680. whether it is executing on behalf of a player or a machine.
  1681.  
  1682. Machines are created using "CreateMachine". It takes a thing, which
  1683. will become the main thing for the machine just like player characters
  1684. have a main thing which hold their properties. It also takes a second
  1685. thing which is the room to create the machine in, and an action to
  1686. execute on behalf of the new machine to start the machine running.
  1687. Note that CreateMachine creates a new data structure for the machine,
  1688. which is stored in the database and manipulated by the server, but the
  1689. new structure is not visible to scenario programmers other than
  1690. through a few specific builtin functions. Those functions are:
  1691.  
  1692.     CreateMachine - create and start a new machine
  1693.  
  1694.     DestroyMachine - destroy a machine
  1695.  
  1696.     FindMachineIndexed - globally find a machine
  1697.  
  1698.     SetMachineActive - set the action which the system will call
  1699.     automatically whenever the server is restarted. This allows
  1700.     the machine to start itself going again.
  1701.  
  1702.     SetMachineIdle - set the action which the system will call when
  1703.     the server is shutting down. This allows the machine to
  1704.     properly save its state and prepare for restart.
  1705.  
  1706.     SetMachineOther - set the action which the system will call when
  1707.     any message is sent to the room the machine is in using any of
  1708.     'OPrint', 'ABPrint', 'Pose' or 'Say'. The string so sent is
  1709.     passed as an argument to this routine. This facility is fairly
  1710.     powerful, and allows machines to participate in nearly all
  1711.     activities. However, this power is easy to misuse. The
  1712.     activities this routine sets up can be expensive, so try not
  1713.     to use it unless absolutely necessary. For example, the
  1714.     standard scenario has more specific, hence cheaper, ways of
  1715.     watching who enters and leaves a room. Also, beware of setting
  1716.     up an infinite recusive loop using this facility.
  1717.  
  1718.     SetMachinePose - set the action which the system will call
  1719.     whenever any agent in the same room as the machine does a pose
  1720.     using the "Pose" builtin. The action is passed the entire pose
  1721.     message, so the machine will usually want to split off the
  1722.     name of the agent doing the pose by using SetTail/GetWord.
  1723.  
  1724.     SetMachineSay - set the action which the system will call whenever
  1725.     any agent in the same room as the machine speaks out loud. The
  1726.     action is passed the entire speech message, so it will want to
  1727.     use SetSay to split it up.
  1728.  
  1729.     SetMachineWhisperMe - set the action which the system will call
  1730.     whenever any agent in the same room as the machine whispers
  1731.     specifically to the machine. The action is passed the full
  1732.     whisper message, and so will want to use builtin SetWhisperMe
  1733.     to split it up.
  1734.  
  1735.     SetMachineWhisperOther - set the action which the system will call
  1736.     if the machine overhears someone in the same room whispering
  1737.     to someone else. The action is passed the full whisper
  1738.     message, and so will want to use builtin SetWhisperOther to
  1739.     split it up.
  1740.  
  1741. When machines execute, they have the access rights of the player who
  1742. created them. So, a player can create a machine that can access things
  1743. which other players cannot.
  1744.  
  1745. The standard scenario has five special machines: Packrat, Caretaker,
  1746. Postman, Questor and the rock-pile. More generic machines are used for
  1747. monsters in the Proving Grounds. Some of those monsters have special
  1748. capabilities that others do not. The main difference here is that the
  1749. special machines always exist, but the others are created and
  1750. destroyed in response to player (other other machine!) actions. See
  1751. file "Scenario.txt" for more details on how machines are handled
  1752. there. Here is an example of simple machine that wanders randomly and
  1753. minimally interacts with other agents:
  1754.  
  1755.     /* grab some stuff from the scenario: */
  1756.     use t_util
  1757.  
  1758.     /* a new table to put new symbols in: */
  1759.     private tp_frog CreateTable().
  1760.     use tp_frog
  1761.  
  1762.     /* the routine which Frog executes on each "step": */
  1763.     define tp_frog proc frogStep()void:
  1764.     int direction;
  1765.  
  1766.     if not ClientsActive() then
  1767.         After(60, frogStep);
  1768.     else
  1769.         direction := Random(12);
  1770.         if TryToMove(direction) then
  1771.         MachineMove(direction);
  1772.         fi;
  1773.         After(10 + Random(10), frogStep);
  1774.     fi;
  1775.     corp;
  1776.  
  1777.     /* the routine used to start up Frog */
  1778.     define tp_frog proc frogStart()void:
  1779.  
  1780.     After(10, frogStep);
  1781.     corp;
  1782.  
  1783.     /* the routine used to restart Frog: */
  1784.     define tp_frog proc frogRestart()void:
  1785.  
  1786.     After(10, frogStep);
  1787.     corp;
  1788.  
  1789.     /* the routine to handle Frog overhearing normal speech */
  1790.     define tp_frog proc frogHear(string what)void:
  1791.     string speaker, word;
  1792.  
  1793.     speaker := SetSay(what);
  1794.     /* Frog croaks if he hears his name */
  1795.     while
  1796.         word := GetWord();
  1797.         word ~= ""
  1798.     do
  1799.         if word == "Frog" then
  1800.         DoSay("Croak!");
  1801.         fi;
  1802.     od;
  1803.     corp;
  1804.  
  1805.     /* the routine to handle someone whispering to Frog: */
  1806.     define tp_frog proc frogWhispered(string what)void:
  1807.     string whisperer, word;
  1808.  
  1809.     whisperer := SetWhisperMe(what);
  1810.     /* Frog ribbets if he is whispered his name */
  1811.     while
  1812.         word := GetWord();
  1813.         word ~= ""
  1814.     do
  1815.         if word == "Frog" then
  1816.         DoSay("Ribbet!");
  1817.         fi;
  1818.     od;
  1819.     corp;
  1820.  
  1821.     /* the routine to handle Frog overhearing a whisper: */
  1822.     define tp_frog proc frogOverhear(string what)void:
  1823.     string whisperer, whisperedTo, word;
  1824.  
  1825.     whisperer := SetWhisperOther(what);
  1826.     whisperedTo := GetWord();
  1827.     /* Frog simply blabs out loud whatever he overhears. */
  1828.     DoSay(whisperer + " whispered to " + whisperedTo + ": " + GetTail());
  1829.     corp;
  1830.  
  1831.     /* the routine to see someone doing a pose: */
  1832.     define tp_frog proc frogSaw(string what)void:
  1833.     string poser, word;
  1834.  
  1835.     SetTail(what);
  1836.     poser := GetWord();
  1837.     /* Frog gets excited if you reference him in a pose. */
  1838.     while
  1839.         word := GetWord();
  1840.         word ~= ""
  1841.     do
  1842.         if word == "Frog" then
  1843.         Pose("jumps up and down excitedly.");
  1844.         fi;
  1845.     od;
  1846.     corp;
  1847.  
  1848.     /* the function to create the Frog: */
  1849.     define tp_frog proc createFrog(thing where)void:
  1850.     thing frog;
  1851.  
  1852.     frog := CreateThing(nil);
  1853.     CreateMachine("Frog", frog, where, frogStart);
  1854.     ignore SetMachineActive(frog, frogRestart);
  1855.     ignore SetMachineSay(frog, frogHear);
  1856.     ignore SetMachineWhisperMe(frog, frogWhispered);
  1857.     ignore SetMachineWhisperOther(frog, frogOverhear);
  1858.     ignore SetMachinePose(frog, frogSaw);
  1859.     corp;
  1860.  
  1861.     /* create Frog and start him up: */
  1862.     createFrog(Here()).
  1863.  
  1864. This code calls functions "TryToMove", "MachineMove" and "DoSay" from
  1865. the standard scenario. They are used so that this Frog machine will
  1866. operate correctly within that scenario. The code here does not
  1867. reference anything else from the standard scenario. In particular,
  1868. Frog doesn't have a description, although CreateMachine will have
  1869. attached "Frog" as his p_pName. Because Frog just moves in a random
  1870. direction, he can take a while to get out of rooms that have only one
  1871. exit.
  1872.  
  1873.  
  1874. Dealing With Agents in General
  1875.  
  1876.  
  1877. As mentioned above, an agent is either a player character or a
  1878. machine. All agents have a current location (or 'nil' if they are not
  1879. in any room), and can ask the system to execute actions on their
  1880. behalf at a later time. Some builtins and some scenario code is setup
  1881. so that it assumes it is executing on behalf of the agent that it is
  1882. affecting. Sometimes it becomes necessary to make an agent execute
  1883. such code, even though that agent is not currently active. The
  1884. "ForceAction" builtin is provided for that purpose. It temporarily
  1885. switches identity to that of the agent to be forced, and then executes
  1886. the action passed. After the action is complete, the identity will be
  1887. switched back to that of the agent who ran ForceAction. Note that this
  1888. kind of thing can be nested, so the scenario programmer must be
  1889. careful to not accidentally cause infinite nesting.
  1890.  
  1891. There are a number of basic builtins for dealing with agents:
  1892.  
  1893.     After - trigger an action to happen in the future
  1894.     AgentLocation - return the location of the specified agent
  1895.     ForceAction - force an agent to execute an action
  1896.     ForEachAgent - execute an action for each agent in a room
  1897.     ForEachClient - execute an action for each active client
  1898.     Here - return the location of the active agent
  1899.     Pose - have the active agent do a pose
  1900.     Say - have the active agent speak out loud
  1901.     SetAgentLocation - move the specified agent to a room
  1902.     SetLocation - move the active agent to a room
  1903.     Whisper - have the active agent whisper to another
  1904.     FindAgent - find an agent by name in the current room
  1905.     FindAgentAt - find an agent by name in some other room
  1906.  
  1907. Sometimes a scenario needs to find an agent matching some kind of
  1908. specification. For example, when trying to determine if the current
  1909. room is dark or not, the scenario wants to see if any agent in the
  1910. room is glowing, or is carrying something that is glowing. This kind
  1911. of search can be done using some global variables (properties on some
  1912. fixed thing) and ForEachAgent. Such a search can be expensive,
  1913. however, so AmigaMUD provides a number of builtin functions that can
  1914. perform some searches more efficiently.
  1915.  
  1916.     FindAgentAsChild - find an agent with a given parent
  1917.     FindAgentAsDescendant - find an agent with a given ancestor
  1918.     FindAgentWithChildOnList - e.g. find agent carrying something
  1919.     FindAgentWithFlag - find an agent with a flag set
  1920.     FindAgentWithFlagOnList - e.g. find agent carrying glowing object
  1921.     FindAgentWithNameOnList - e.g. find agent carrying an "xxxx"
  1922.  
  1923.  
  1924. Symbol Functions
  1925.  
  1926.  
  1927. Tables in AmigaMUD are usually only needed by advanced scenario
  1928. programmers. For example, the standard scenario uses them in the build
  1929. code. The relevant builtin functions are:
  1930.  
  1931.     DefineAction - enter an action into a table
  1932.     DefineCounter - enter an int property into a table
  1933.     DefineFlag - enter a bool property into a table
  1934.     DefineString - enter a string property into a table
  1935.     DefineTable - enter a table into a table
  1936.     DefineThing - enter a thing into a table
  1937.     DeleteSymbol - delete a symbol from a table
  1938.     DescribeSymbol - describe a symbol in a table
  1939.     FindActionSymbol - find a symbol for an action
  1940.     FindThingSymbol - find a symbol for a thing
  1941.     IsDefined - test if a symbol is defined in a table
  1942.     LookupAction - look up an action symbol
  1943.     LookupCounter - look up an int property symbol
  1944.     LookupFlag - look up a bool property symbol
  1945.     LookupString - look up a string property symbol
  1946.     LookupTable - look up a table in another table
  1947.     LookupThing - look up a thing in a table
  1948.     MoveSymbol - move a symbol from one table to another
  1949.     RenameSymbol - rename a symbol in a table
  1950.     ScanTable - call a function for each symbol in a table
  1951.     ShowTable - show the symbols in a table
  1952.     UnUseTable - remove a table from the "in use" list
  1953.     UseTable - add a table to the "in use" list
  1954.  
  1955.  
  1956. Security Issues
  1957.  
  1958.  
  1959. Security is an odd thing to worry about in a game, but there are some
  1960. aspects of security that arise in a game like AmigaMUD. The first
  1961. aspect is that of the security of the system running the server. The
  1962. builtin function "Execute" allows any arbitrary string to be executed
  1963. as an AmigaDOS command. This can be quite dangerous if not protected
  1964. properly. Second, the system should allow one wizard or apprentice to
  1965. protect his internal structures and properties from tampering by other
  1966. wizards or apprentices. Third, the scenario itself should be
  1967. constructed so as to not allow "cheating". For example, there
  1968. shouldn't be a way for a player to easily get lots of experience in
  1969. the Proving Grounds. This can be considered to be unfair to other
  1970. players who do not try to get around the proper methods.
  1971.  
  1972. There is very little that can be said about the third aspect of
  1973. security - if the scenario allows cheating, then it has a bug. The
  1974. reader is warned that there are things in the standard scenario that
  1975. players can take advantage of, some of which might seem quite
  1976. surprising at first. I have fixed all of the methods that I know of
  1977. and want fixed. I have deliberately left some very minor methods of
  1978. cheating alone, and in one case, have carefully constructed things to
  1979. allow something that some might consider cheating.
  1980.  
  1981. The first aspect of security, that of preventing people from doing
  1982. things like formatting the hard drive of the host system, is the most
  1983. important. The basic protection mechanism here is that of restricting
  1984. such functions to be only executable by the special character
  1985. SysAdmin, or by code owned by SysAdmin. A further restriction is that
  1986. by default the system will not allow SysAdmin to login remotely. In
  1987. any case, the owner of a system running the AmigaMUD server should
  1988. never give out the password to the SysAdmin character. Also, the owner
  1989. should change SysAdmin's password to something other than the standard
  1990. one that the system is shipped with.
  1991.  
  1992. The person who runs the host system, and has access to SysAdmin,
  1993. should also be quite careful with scenario code he/she writes. Such
  1994. code, unless setup otherwise, runs with the full privileges of
  1995. SysAdmin. This is required in cases like the usenet access code, since
  1996. Execute is needed to call up the various UUCP commands. Be very
  1997. careful with any code which calls Execute, or which writes to files on
  1998. the server. Do not write and publish (by making it available in a
  1999. table that others can "use") a function which calls Execute with its
  2000. parameter. Do not attach such a function to things as a property whose
  2001. name others can see. Do not add such a function to a list of functions
  2002. that others can get at. Program defensively, and use Execute and
  2003. FileOpenForWrite as little as possible.
  2004.  
  2005. Some of the builtin functions in AmigaMUD can only be executed by
  2006. SysAdmin. This is not visible by inspecting the functions, but is
  2007. enforced at runtime by the functions themselves. The restricted
  2008. functions are:
  2009.  
  2010.     DescribeKey         (real and effective)
  2011.     SetNewCharacterAction    (real and effective)
  2012.     SetSingleUser        (real and effective)
  2013.     SetRemoteSysAdminOK     (real and effective)
  2014.     SetMachinesActive        (real and effective)
  2015.     CreateCharacter        (effective)
  2016.     DestroyCharacter        (effective)
  2017.     NewCreationPassword     (real and effective)
  2018.     FindKey            (real and effective)
  2019.     DumpThing            (real and effective)
  2020.     SetContinue         (real and effective)
  2021.     ShutDown            (real and effective)
  2022.     APrint            (effective)
  2023.     FileOpenForRead        (effective)
  2024.     FileOpenForWrite        (effective)
  2025.     FileOpenForUpdate        (effective)
  2026.     FileClose            (effective)
  2027.     FileRead            (effective)
  2028.     FileReadBinary        (effective)
  2029.     FileWrite            (effective)
  2030.     FileWriteBinary        (effective)
  2031.     FileSeek            (effective)
  2032.     Execute            (effective)
  2033.  
  2034. The functions marked as "(real and effective)" can only be used if
  2035. both the real and effective user are SysAdmin, i.e. only by SysAdmin
  2036. executing at the command level or executing functions owned by
  2037. SysAdmin. Those marked "(effective)" can be executed by anyone if they
  2038. are in a function owned by SysAdmin which is not marked "utility" (see
  2039. below).
  2040.  
  2041. The way for a wizard or apprentice to protect their code and data
  2042. structures from others is to not make them visible to others. A
  2043. function in your private symbol table, or in a table within your
  2044. private symbol table, is not visible to others unless you do something
  2045. to make it so. The same is true for properties. Note that there are no
  2046. functions in AmigaMUD that allow you to retrieve the property itself
  2047. from a thing. You can only modify or retrieve the value of a property
  2048. from a thing if you have access to the property itself. Thus, if you
  2049. never make the name of a property publically available, no-one other
  2050. than yourself (and SysAdmin) can see or change the values of that
  2051. property on things it gets attached to. They can know that a given
  2052. thing has properties that they cannot see, but that is all. This can
  2053. be seen by logging on as a wizard or apprentice and dumping out your
  2054. character's thing (using the "describe" wizard-mode command), while
  2055. changing the set of tables "in use".
  2056.  
  2057. When defining a function, you can specify either or both of "public"
  2058. and "utility" between the "proc" and the name of the function. If you
  2059. make a function "public" in this way, then its definition can be seen
  2060. by others using the "describe" command (or the DescribeSymbol
  2061. builtin function). If you do not make the function "public", then
  2062. others can only see its header.
  2063.  
  2064. When a function is called in AmigaMUD, the system will normally change
  2065. the active access rights to those of the owner of the function. This
  2066. allows the function to retrieve and modify properties from things
  2067. owned by the owner of the function. When the function returns, the
  2068. access rights are reset to what they were before the function was
  2069. called. This access rights changing is done in a fully nested way, for
  2070. as many function calls as are needed. If a function is marked as
  2071. "utility", then the access rights are not changed when the function is
  2072. called. This allows programmers to write functions that have no more
  2073. access than the player (or the function calling their functions) would
  2074. normally have. As an example, the input parser in the standard
  2075. scenario is "utility", as are most of the functions that implement the
  2076. build facility. This is done so that when objects and properties are
  2077. created by the build code, they belong to the active player, and not
  2078. to SysAdmin. In some cases (e.g. objects), things are explicitly given
  2079. to SysAdmin (using "GiveThing"), so that they can be accessed properly
  2080. by other players. There isn't much point in going to a lot of work to
  2081. create something if no other player can appreciate it!
  2082.  
  2083. The AmigaMUD server maintains two sets of owner and status variables.
  2084. One is the "real" value, and one is the "effective" value. The
  2085. "effective" values are the ones changed when a non-"utility" function
  2086. is executed. The "real" values are unchanged. The differences between
  2087. the two include those mentioned above relating to builtins that can
  2088. only be executed by SysAdmin. The builtin "Me" returns the "real"
  2089. user. Builtin "Mine" tests against the "effective" player. When
  2090. properties and things, etc. are created they are owned by the
  2091. "effective" player. Access checks are done against the "effective"
  2092. player also.
  2093.  
  2094. Wizards and apprentices should be careful with the idea of making
  2095. things secure, however. A violation of security will result in the
  2096. abortion of execution. So, a normal player using an object, room, or
  2097. whatever, in the the normal way, should not get any security
  2098. violations. In effect this simply means that you should test all of
  2099. your creations with some other normal character.
  2100.  
  2101. Each thing in the database has a status, which governs who can
  2102. retrieve and modify the properties on it (assuming they have access to
  2103. some name for the properties themselves). These modes, also discussed
  2104. elsewhere, are:
  2105.  
  2106.     ts_public - anyone can retrieve or modify properties. A surprising
  2107.     number of things have to have this full access in AmigaMUD.
  2108.     For example, all objects should be owned by SysAdmin and have
  2109.     status ts_public. All characters have status ts_public. This
  2110.     is needed so that scenario code written by someone other than
  2111.     SysAdmin can have an effect on objects and characters. There
  2112.     would be little point to the game if this were not so!
  2113.  
  2114.     ts_private - only the owner of a thing can retrieve or modify
  2115.     properties on the object. This status is only needed if you
  2116.     need to make a thing public, but you don't want others to be
  2117.     able to mess with it. Normally, you would make a thing private
  2118.     by simply not letting anyone else get a pointer to it.
  2119.  
  2120.     ts_readonly - only the owner of a thing can change it, but
  2121.     everyone can retrieve properties from it. This is useful when
  2122.     you want to provide a standard object or set of properties to
  2123.     others, but you don't want them able to mess it up. For
  2124.     example, the standard rooms, "r_indoors", "r_outdoors", etc.
  2125.     are set this way in the standard scenario.
  2126.  
  2127.     ts_wizard - an object which is ts_wizard can have its properties
  2128.     retrieved by anyone, but only full wizards can change the
  2129.     properties. This provides an intermediate level of safety, by
  2130.     assuming that only experienced AmigaMUD programmers (or the
  2131.     owner of the system!) are full wizards, and are thus less
  2132.     likely to mess things up.
  2133.  
  2134. There are three classes of character in AmigaMUD: normal, apprentice
  2135. and wizard. Normal characters cannot enter wizard mode, and so cannot
  2136. normally write AmigaMUD programs or define properties, etc. The build
  2137. code in the standard scenario allows anyone to produce simple
  2138. functions, define some properties, etc. in a controlled way. This is
  2139. enabled by setting the "p_pBuilder" flag on the character. However,
  2140. everyone is enabled for building inside the PlayPen room, or in any
  2141. room which is built by someone in a playpen room.
  2142.  
  2143. The concept of an apprentice was added at the insistence of a friend
  2144. (who has yet to do anything significant with AmigaMUD!) The intent
  2145. of what I implemented is to provide programming access to more people,
  2146. while providing other players with some protection from the "errors"
  2147. that apprentices might make. A number of builtin functions cannot be
  2148. used in functions written by apprentices. The choice of these has been
  2149. quite arbitrary, and I am open to reasons for changing the set. The
  2150. current set is:
  2151.  
  2152.     ChangeName
  2153.     Log
  2154.     NewCharacterPassword
  2155.     SetAgentLocation
  2156.     SetCharacterActiveAction
  2157.     SetCharacterButtonAction
  2158.     SetCharacterEffectDoneAction
  2159.     SetCharacterIdleAction
  2160.     SetCharacterInputAction
  2161.     SetCharacterMouseDownAction
  2162.     SetCharacterRawKeyAction
  2163.     SetIndent
  2164.     SetLocation
  2165.     SetPrompt
  2166.     TextHeight
  2167.     TextWidth
  2168.     Trace
  2169.  
  2170. Some of the locations in the more important parts of the standard
  2171. scenario have been set to ts_wizard. This means that full wizards can
  2172. build from them, but apprentices can't.
  2173.  
  2174. One of the "games" that people play in some MUDs is to create output
  2175. that looks the same as normal output from normal commands, in an
  2176. attempt to fool other players into thinking things are happening that
  2177. in fact are not. In AmigaMUD, I have tried to not let this happen
  2178. unless the victim allows it. Any output line which contains text which
  2179. is produced by a function owned by an apprentice will be prefixed by
  2180. an "@", thus warning the player that something might be suspicious.
  2181. The player can disable these warnings.
  2182.  
  2183. When players are created, they are normal. Only a wizard or apprentice
  2184. can promote a player, and an apprentice can only promote a player to
  2185. apprentice status. The system remembers who promoted a player, and
  2186. this can be seen when the character is displayed. Similarly, only
  2187. SysAdmin can demote players. Thus, since SysAdmin is initially the
  2188. only non-normal player, SysAdmin has control over who can program in
  2189. the MUD.
  2190.  
  2191.  
  2192. Efficiency Considerations
  2193.  
  2194.  
  2195. The AmigaMUD programming language is an interpreter. This means that
  2196. it will not execute as fast as compiled or assembled code. It is
  2197. reasonably efficient, however, so executing a couple hundred lines of
  2198. code in response to an input command is not a problem. There are a few
  2199. things that should be taken into consideration when writing AmigaMUD
  2200. code, so as to keep things as efficient as possible.
  2201.  
  2202. The first thing to do is to try to avoid doing things more than once
  2203. when once is enough. This rule will make just about any program more
  2204. efficient, regardless of what language it is written in. In a system
  2205. like AmigaMUD, where some operations are quite a bit more expensive
  2206. than others, the rule is more important if expensive operations are
  2207. being repeated. For example, if you want to get some numbers from
  2208. strings and operate on them, it is much more efficient to use builtin
  2209. functions to get the numbers, and then operate on them as int values,
  2210. than to operate on them using the more expensive string operations.
  2211.  
  2212. Two operations in AmigaMUD are expensive. The first is database
  2213. accesses. Even though the database is cached, and subsequent fetches
  2214. of a given property, thing, etc. will "hit" in the cache, there is
  2215. still a lot of server code executed to retrieve something from the
  2216. cache. For example, instead of doing:
  2217.  
  2218.     private p_weight CreateIntProp().
  2219.     private p_capacity CreateIntProp().
  2220.     ...
  2221.     private proc tryToCarry(thing person, object)bool:
  2222.  
  2223.     if object@p_weight > person@p_capacity then
  2224.         Print("There is no way you can pick that up!\n");
  2225.         false
  2226.     elif person@p_weight + object@p_weight > person@p_capacity
  2227.     then
  2228.         Print("You can't pick that up now.\n");
  2229.         false
  2230.     else
  2231.         person@p_weight := person@p_weight + object@p_weight;
  2232.         true
  2233.     fi
  2234.     corp;
  2235.  
  2236. it is more efficient (although perhaps slightly less readable) to do:
  2237.  
  2238.     private p_weight CreateIntProp().
  2239.     private p_capacity CreateIntProp().
  2240.     ...
  2241.     private proc tryToCarry(thing person, object)bool:
  2242.     int capacity, current, objWeight;
  2243.  
  2244.     capacity := person@p_capacity
  2245.     current := person@p_weight;
  2246.     objWeight := object@p_weight;
  2247.     if objWeight > capacity then
  2248.         Print("There is no way you can pick that up!\n");
  2249.         false
  2250.     elif current + objWeight > capacity then
  2251.         Print("You can't pick that up now.\n");
  2252.         false
  2253.     else
  2254.         person@p_weight := current + objWeight;
  2255.         true
  2256.     fi
  2257.     corp;
  2258.  
  2259. Note that the changed value must be stored back to the true property,
  2260. of course! If a property is going to be used only once in a function,
  2261. then it is better to not put it into a temporary variable, but if it
  2262. is going to be used more than once, it is quicker to put it into a
  2263. temporary variable. For a short routine that is only going to use a
  2264. property twice, and that is called infrequently, it likely isn't worth
  2265. the effort of making temporary variables.
  2266.  
  2267. The second expensive operation in AmigaMUD is that of calling
  2268. functions, either user functions or builtin functions. User functions
  2269. are more expensive than builtin functions, however, unless the builtin
  2270. function is one that does a lot of work, or results in messages being
  2271. sent to one or more clients. The same technique of keeping copies of
  2272. values in temporary variables can be used to avoid unnecessary calls
  2273. to some builtins. The most commonly "cached" values are Me() and
  2274. Here(), as in:
  2275.  
  2276.     private proc doSomethingOrOther()void:
  2277.     thing me, here, it;
  2278.  
  2279.     me := Me();
  2280.     here := Here();
  2281.     it := It();
  2282.     ...
  2283.     .. several uses of "me", "here" and "it" ..
  2284.  
  2285. Note however, that if your function changes the value returned by one
  2286. of these builtins, you should also change your temporary variable:
  2287.  
  2288.     private proc blahBlahBlah()void:
  2289.     thing me, here;
  2290.  
  2291.     me := Me();
  2292.     here := Here();
  2293.     ...
  2294.     SetLocation(here@p_rNorth);    /* changes Here() */
  2295.     here := Here();
  2296.     ...
  2297.     corp;
  2298.  
  2299. Another aspect of efficiency is that of sending messages to multiple
  2300. remote clients. There is some overhead involved in each message that
  2301. is sent, so it is worthwhile to try to cut down on extra ones. For
  2302. example, if you are doing something that affects the displays of
  2303. everyone in the room, you should try to do all things for a given
  2304. client before going on to the next one. The fact that "ForEachAgent"
  2305. is an expensive builtin adds to the desireabilty of doing this. Keep
  2306. in mind that the active player is a client just like any other.
  2307.  
  2308. Some specific things to watch out for:
  2309.  
  2310.     - avoid using a 'nil' location on ForEachAgent. Try to keep your
  2311.     actions restricted to the agents in a given room.
  2312.  
  2313.  
  2314. Limitations of AmigaMUD
  2315.  
  2316.  
  2317. It would be nice to say that there are no limitations in AmigaMUD, but
  2318. that isn't correct. Where I have had to make a choice between creating
  2319. a limitation and creating considerable inefficiency, I have usually
  2320. chosen to create a minor limitation. Under most circumstances, the
  2321. limitations you will have in running AmigaMUD will be the amount of
  2322. memory and disk space that you have available. There are a few hard,
  2323. fixed limits, however:
  2324.  
  2325.     thing: a maximum of 255 properties
  2326.  
  2327.     table: a maximum of 65535 entries
  2328.     grammar: same as table
  2329.  
  2330.     character: limitations on the attached thing, plus
  2331.     name is limited to 20 characters
  2332.     password is limited to 20 characters
  2333.     machine: same as character
  2334.  
  2335.     proc (action): 65535 bytes of locals
  2336.     (about 16,000 local variables and parameters)
  2337.  
  2338.     list: a maximum of 65535 elements
  2339.  
  2340.     database entry: 65535 bytes
  2341.     (this restricts lists to about 16,000 elements)
  2342.  
  2343.     database: 16 million entries
  2344.  
  2345.     strings: limited by the code to about 4000 characters
  2346.  
  2347. The only serious limit is that on things. The limit on lists is a
  2348. dangerous one, since the system will not be able to handle large lists
  2349. unless a very large cache is given to it. This is since the entire
  2350. list (4 bytes per element) has to be in contiguous space in the cache
  2351. (and in the database). Appending an element to such a list would
  2352. require 2 such regions.
  2353.  
  2354. If you have a complex quest and want to keep lots of flags and
  2355. properties, think about this alternative: create a new thing for each
  2356. player who enters your quest. Attach the new thing to the player as a
  2357. single property, and store your many properties on the new thing. That
  2358. way, you can feel free to use the full 255 properties to record the
  2359. player's progress in your quest. Remember that your quest may not be
  2360. the only large one in the MUD - it would be very frustrating for the
  2361. players if entering your quest and playing around a bit made it
  2362. impossible for them to complete other quests (or vice versa).
  2363.  
  2364. There is a fairly serious, hidden limit. Because a given entry in the
  2365. database cannot be more than 65535 bytes long, a given table cannot
  2366. need more than that number of bytes in total. This includes the text
  2367. of all entries, and 6 bytes per table entry. This is the easiest limit
  2368. to reach accidentally. Because of this, do not let any of your source
  2369. files get larger than the ones in the standard scenario. This also
  2370. implies that if you add a lot to any of the larger standard scenario
  2371. source files, you should consider splitting that file up so that it
  2372. uses more than one table for its symbols. Putting more symbols into
  2373. private tables is a good way to do this. In many cases, the symbols
  2374. are in public tables simply because there is no other good reason to
  2375. keep them private.
  2376.